Merge pull request #1275 from dibdot/travelmate
authorHannu Nyman <hannu.nyman@iki.fi>
Thu, 3 Aug 2017 15:29:48 +0000 (18:29 +0300)
committerGitHub <noreply@github.com>
Thu, 3 Aug 2017 15:29:48 +0000 (18:29 +0300)
luci-app-travelmate: small bugfixes & optimizations

80 files changed:
applications/luci-app-aria2/po/sv/aria2.po
applications/luci-app-attendedsysupgrade/Makefile [new file with mode: 0644]
applications/luci-app-attendedsysupgrade/luasrc/controller/attendedsysupgrade.lua [new file with mode: 0644]
applications/luci-app-attendedsysupgrade/luasrc/view/attendedsysupgrade.htm [new file with mode: 0644]
applications/luci-app-clamav/po/sv/clamav.po
applications/luci-app-commands/po/sv/commands.po
applications/luci-app-ddns/po/sv/ddns.po
applications/luci-app-diag-core/po/sv/diag_core.po
applications/luci-app-mwan3/po/ja/mwan3.po
applications/luci-app-nlbwmon/Makefile [new file with mode: 0644]
applications/luci-app-nlbwmon/htdocs/luci-static/resources/nlbw.chart.min.js [new file with mode: 0644]
applications/luci-app-nlbwmon/luasrc/controller/nlbw.lua [new file with mode: 0644]
applications/luci-app-nlbwmon/luasrc/model/cbi/nlbw/config.lua [new file with mode: 0644]
applications/luci-app-nlbwmon/luasrc/view/nlbw/backup.htm [new file with mode: 0644]
applications/luci-app-nlbwmon/luasrc/view/nlbw/display.htm [new file with mode: 0644]
applications/luci-app-nlbwmon/po/ja/nlbwmon.po [new file with mode: 0644]
applications/luci-app-nlbwmon/po/templates/nlbwmon.pot [new file with mode: 0644]
applications/luci-app-nlbwmon/root/etc/uci-defaults/40_luci-nlbwmon [new file with mode: 0644]
applications/luci-app-shadowsocks-libev/luasrc/model/shadowsocks-libev.lua
applications/luci-app-statistics/po/ca/statistics.po
applications/luci-app-statistics/po/cs/statistics.po
applications/luci-app-statistics/po/de/statistics.po
applications/luci-app-statistics/po/el/statistics.po
applications/luci-app-statistics/po/en/statistics.po
applications/luci-app-statistics/po/es/statistics.po
applications/luci-app-statistics/po/fr/statistics.po
applications/luci-app-statistics/po/he/statistics.po
applications/luci-app-statistics/po/hu/statistics.po
applications/luci-app-statistics/po/it/statistics.po
applications/luci-app-statistics/po/ja/statistics.po
applications/luci-app-statistics/po/ms/statistics.po
applications/luci-app-statistics/po/no/statistics.po
applications/luci-app-statistics/po/pl/statistics.po
applications/luci-app-statistics/po/pt-br/statistics.po
applications/luci-app-statistics/po/pt/statistics.po
applications/luci-app-statistics/po/ro/statistics.po
applications/luci-app-statistics/po/ru/statistics.po
applications/luci-app-statistics/po/sk/statistics.po
applications/luci-app-statistics/po/sv/statistics.po
applications/luci-app-statistics/po/templates/statistics.pot
applications/luci-app-statistics/po/tr/statistics.po
applications/luci-app-statistics/po/uk/statistics.po
applications/luci-app-statistics/po/vi/statistics.po
applications/luci-app-statistics/po/zh-cn/statistics.po
applications/luci-app-statistics/po/zh-tw/statistics.po
applications/luci-app-travelmate/po/ja/travelmate.po
applications/luci-app-travelmate/po/pt-br/travelmate.po
applications/luci-app-travelmate/po/templates/travelmate.pot
applications/luci-app-watchcat/po/sv/watchcat.po
applications/luci-app-wifischedule/po/sv/wifischedule.po
applications/luci-app-wireguard/po/sv/wireguard.po
modules/luci-base/htdocs/luci-static/resources/cbi.js
modules/luci-base/htdocs/luci-static/resources/xhr.js
modules/luci-base/luasrc/sys.lua
modules/luci-base/po/ca/base.po
modules/luci-base/po/cs/base.po
modules/luci-base/po/de/base.po
modules/luci-base/po/el/base.po
modules/luci-base/po/en/base.po
modules/luci-base/po/es/base.po
modules/luci-base/po/fr/base.po
modules/luci-base/po/he/base.po
modules/luci-base/po/hu/base.po
modules/luci-base/po/it/base.po
modules/luci-base/po/ja/base.po
modules/luci-base/po/ko/base.po
modules/luci-base/po/ms/base.po
modules/luci-base/po/no/base.po
modules/luci-base/po/pl/base.po
modules/luci-base/po/pt-br/base.po
modules/luci-base/po/pt/base.po
modules/luci-base/po/ro/base.po
modules/luci-base/po/ru/base.po
modules/luci-base/po/sk/base.po
modules/luci-base/po/sv/base.po
modules/luci-base/po/templates/base.pot
modules/luci-base/po/tr/base.po
modules/luci-base/po/uk/base.po
modules/luci-base/po/vi/base.po
modules/luci-base/po/zh-tw/base.po

index a7f41f2..3a12936 100644 (file)
@@ -1,5 +1,5 @@
 msgid ""
-msgstr "Content-Type: text/plain; charset=UTF-8"
+msgstr "Content-Type: text/plain; charset=UTF-8\n"
 
 msgid "\"Falloc\" is not available in all cases."
 msgstr ""
diff --git a/applications/luci-app-attendedsysupgrade/Makefile b/applications/luci-app-attendedsysupgrade/Makefile
new file mode 100644 (file)
index 0000000..32992e1
--- /dev/null
@@ -0,0 +1,11 @@
+# See /LICENSE for more information.
+# This is free software, licensed under the GNU General Public License v2.
+
+include $(TOPDIR)/rules.mk
+
+LUCI_TITLE:=LuCI support for attended sysupgrades
+LUCI_DEPENDS:=+luci-base +rpcd-mod-attendedsysupgrade
+
+include ../../luci.mk
+
+# call BuildPackage - OpenWrt buildroot signature
diff --git a/applications/luci-app-attendedsysupgrade/luasrc/controller/attendedsysupgrade.lua b/applications/luci-app-attendedsysupgrade/luasrc/controller/attendedsysupgrade.lua
new file mode 100644 (file)
index 0000000..1bd050a
--- /dev/null
@@ -0,0 +1,5 @@
+module("luci.controller.attendedsysupgrade", package.seeall)
+
+function index()
+        entry({"admin", "system", "attended_sysupgrade"}, template("attendedsysupgrade"), _("Attended Sysupgrade"), 1)
+end
diff --git a/applications/luci-app-attendedsysupgrade/luasrc/view/attendedsysupgrade.htm b/applications/luci-app-attendedsysupgrade/luasrc/view/attendedsysupgrade.htm
new file mode 100644 (file)
index 0000000..8bcec3b
--- /dev/null
@@ -0,0 +1,266 @@
+<%+header%>
+<h2 name="content"><%:Attended Sysupgrade%></h2>
+<div  class="container">
+       <div style="display: none" id="update_info" class="alert-message info">
+       </div>
+       <div style="display: none" id="update_error" class="alert-message danger">
+       </div>
+</div>
+<input class="cbi-button" value="search for updates" onclick="update_request()" type="button" id="update_button">
+
+<script type="text/javascript">
+
+latest_release = "";
+data = {};
+ubus_counter = 1
+origin = document.location.href.replace(location.pathname, "")
+ubus_url = origin + "/ubus/"
+
+// requests to the update server
+function server_request(request_dict, path, callback) {
+       url = data.update_server + "/" + path
+       request_dict.distro = data.release.distribution;
+       request_dict.target = data.release.target.split("\/")[0];
+       request_dict.subtarget = data.release.target.split("\/")[1];
+       request_dict.packages = data.packagelist;
+       var xmlhttp = new XMLHttpRequest();
+       xmlhttp.open("POST", url, true);
+       xmlhttp.setRequestHeader("Content-type", "application/json");
+       xmlhttp.send(JSON.stringify(request_dict));
+       xmlhttp.onerror = function(e) {
+               update_error("update server down")
+       }
+       xmlhttp.addEventListener('load', function(event) {
+               callback(xmlhttp)
+       });
+}
+
+// requests ubus via rpcd
+function ubus_request(command, argument, params, callback) {
+       // perform login if ubus_rpc_session is empty
+       var request_data = '{ "jsonrpc": "2.0", "id": ' + ubus_counter + ', "method": "call", "params": [ "'+ data["ubus_rpc_session"] +'", "' + command + '", "' + argument + '", ' + params + ' ] }'
+       ubus_counter++
+       var xmlhttp = new XMLHttpRequest();
+       xmlhttp.open("POST", ubus_url, true);
+       xmlhttp.setRequestHeader("Content-type", "application/json");
+       xmlhttp.onerror = function(e) {
+               setTimeout(back_online, 5000)
+       }
+       xmlhttp.addEventListener('load', function(event) {
+               if(command === "uci") {
+                       ubus_request_callback_uci(xmlhttp, callback)
+               } else {
+                       ubus_request_callback(xmlhttp, callback)
+               }
+       });
+       xmlhttp.send(request_data);
+}
+
+// handle ubus_requests, set variables or perform functions
+function ubus_request_callback(response_object, callback) {
+       if(response_object.status === 200) {
+               console.log(callback)
+               if(typeof callback === "string") {
+                       response_json = JSON.parse(response_object.responseText).result[1]
+                       data[callback] = response_json[callback]
+               } else {
+                       callback(response_object)
+               }
+       } else {
+               console.log(respons_object.responseText)
+       }
+}
+
+function ubus_request_callback_uci(response_object, callback) {
+       if(response_object.status === 200) {
+               console.log(callback)
+               response_json = JSON.parse(response_object.responseText).result[1].value
+               data[callback] = response_json
+       } else {
+               console.log(respons_object.responseText)
+       }
+}
+
+// initial setup, get system information
+function setup() {
+       data["ubus_rpc_session"] = "<%=luci.dispatcher.context.authsession%>"
+       ubus_request("packagelist", "list", '{  }', "packagelist")
+       ubus_request("system", "board", '{  }', "release")
+       ubus_request("system", "board", '{  }', "board_name")
+       ubus_request("uci", "get", '{"config": "attendedsysupgrade", "section": "@settings[0]", "option": "update_server"}', "update_server")
+}
+
+// shows notification if update is available
+function update_info(info_output) {
+       document.getElementById("update_info").style.display = "block";
+       document.getElementById("update_info").innerHTML = info_output;
+}
+
+function update_error(error_output) {
+       document.getElementById("update_error").style.display = "block";
+       document.getElementById("update_error").innerHTML = error_output;
+       document.getElementById("update_info").style.display = "None";
+}
+
+// asks server for news updates, actually only based on relesae not packages
+function update_request() {
+       console.log("update_request")
+       request_dict = {}
+       request_dict.version = data.release.version;
+       server_request(request_dict, "update-request", update_request_callback)
+}
+
+function update_request_callback(response_object) {
+       if (response_object.status === 500) {
+               // python crashed
+               update_error("internal server error, please try again later")
+               console.log("update server issue")
+       } else if (response_object.status === 502) {
+               // python part offline
+               update_error("internal server error, please try again later")
+               console.log("update server issue")
+       } else if (response_object.status === 503) {
+               // handle overload
+               update_error("server overloaded, retry in 5 minutes")
+               console.log("server overloaded")
+               setTimeout(update_request, 300000)
+       } else if (response_object.status === 201) {
+               update_info("imagebuilder not ready, please wait")
+               console.log("setting up imagebuilder")
+               setTimeout(update_request, 5000)
+       } else if (response_object.status === 204) {
+               // no updates
+               update_info("no updates available")
+       } else if (response_object.status === 400) {
+               // bad request
+               console.log(response_object.responseText)
+               response_object_content = JSON.parse(response_object.responseText)
+               update_error(response_object_content)
+       } else if (response_object.status === 200) {
+               // new release/updates
+               response_object_content = JSON.parse(response_object.responseText)
+               update_request_200(response_object_content)
+       }
+}
+
+function back_online() {
+       ubus_request("session", "login", '{  }', back_online_callback)
+}
+
+function back_online_callback(response_object) {
+       if (response_object.status != 200) {
+               setTimeout(back_online, 5000)
+       } else {
+               update_info("upgrade successfull!")
+               document.getElementById("update_button").value = "reload page";
+               document.getElementById("update_button").onclick = function() { location.reload(); }
+       }
+
+}
+
+function update_request_200(response_content) {
+       info_output = ""
+       if(response_content.version != undefined) {
+               info_output += "new update available. from " + data.release.version + " to " + response_content.version
+               latest_version = response_content.version;
+       }
+       if(response_content.packages != undefined) {
+               info_output += "package updates available"
+       }
+       update_info(info_output)
+       document.getElementById("update_button").value = "request image";
+       document.getElementById("update_button").onclick = image_request;
+}
+
+// request the image, need merge with update_request
+function image_request() {
+       console.log("image_request")
+       request_dict = {}
+       request_dict.version = latest_version;
+       request_dict.board = data.board_name
+       server_request(request_dict, "/image-request", image_request_handler)
+}
+
+function image_request_handler(response) {
+       if (response.status === 400) {
+               response_content = JSON.parse(response.responseText)
+               update_error(response_content.error)
+       } else if (response.status === 500) {
+               image_request_500()
+       } else if (response.status === 503) {
+               update_error("please wait. server overloaded")
+               // handle overload
+               setTimeout(image_request, 30000)
+       } else if (response.status === 201) {
+               response_content = JSON.parse(response.responseText)
+               if(response_content.queue != undefined) {
+                       // in queue
+                       update_info("please wait. you are in queue position " + response_content.queue)
+                       console.log("queued")
+               } else {
+                       update_info("imagebuilder not ready, please wait")
+                       console.log("setting up imagebuilder")
+               }
+               setTimeout(image_request, 5000)
+       } else if (response.status === 206) {
+               // building
+               console.log("building")
+               update_info("building image")
+               setTimeout(image_request, 5000)
+       } else if (response.status === 200) {
+               // ready to download
+               response_content = JSON.parse(response.responseText)
+               update_info("image created")
+               document.getElementById("update_button").value = "download and flash"
+               document.getElementById("update_button").onclick = function() {download_image(response_content.url); }
+       }
+}
+
+
+// uploads received blob data to the server using cgi-io
+function upload_image(blob) {
+       var upload_request = new XMLHttpRequest();
+       var form_data  = new FormData();
+
+       form_data.append("sessionid", data.ubus_rpc_session)
+       form_data.append("filename", "/tmp/sysupgrade.bin")
+       form_data.append("filemode", 755) // insecure?
+       form_data.append("filedata", blob)
+
+       upload_request.addEventListener('load', function(event) {
+               // this checksum should be parsed
+               document.getElementById("update_info").innerHTML = "flashing... please wait" // show fancy indicator http://www.ajaxload.info/
+               ubus_request("attendedsysupgrade", "sysupgrade", '{  }', 'done');
+       });
+
+       upload_request.addEventListener('error', function(event) {
+               document.getElementById("update_info").innerHTML = "uploading failed, please retry"
+       });
+
+       upload_request.open('POST', origin + '/cgi-bin/cgi-upload');
+       upload_request.send(form_data);
+}
+
+// download image from server once the url was received by image_request
+function download_image(url) {
+       console.log("download_image")
+       document.getElementById("update_button").value = "flashing..."
+       document.getElementById("update_button").disabled = true;
+       var download_request = new XMLHttpRequest();
+       download_request.open("GET", url);
+       download_request.responseType = "arraybuffer";
+
+       download_request.onload = function () {
+               if (this.status === 200) {
+                       var blob = new Blob([download_request.response], {type: "application/octet-stream"});
+                       upload_image(blob)
+               }
+       };
+       document.getElementById("update_info").innerHTML = "downloading image"
+       download_request.send();
+}
+
+document.onload = setup()
+</script>
+
+<%+footer%>
index 589d5f9..37de249 100644 (file)
@@ -1,5 +1,5 @@
 msgid ""
-msgstr "Content-Type: text/plain; charset=UTF-8"
+msgstr "Content-Type: text/plain; charset=UTF-8\n"
 
 msgid "10"
 msgstr "10"
index 8cb1923..a944fdb 100644 (file)
@@ -104,8 +104,8 @@ msgid ""
 "This page allows you to configure custom shell commands which can be easily "
 "invoked from the web interface."
 msgstr ""
-"Den här sidan tillåter dig att ställa in anpassade skalkommandon som lättast kan "
-"åberopas från webbgränssnittet."
+"Den här sidan tillåter dig att ställa in anpassade skalkommandon som lättast "
+"kan åberopas från webbgränssnittet."
 
 msgid "Waiting for command to complete..."
 msgstr "Väntar på att kommandot ska slutföras..."
index 780a2f9..9373fea 100644 (file)
@@ -1,5 +1,5 @@
 msgid ""
-msgstr "Content-Type: text/plain; charset=UTF-8"
+msgstr "Content-Type: text/plain; charset=UTF-8\n"
 
 msgid "&"
 msgstr "&"
@@ -237,8 +237,8 @@ msgid ""
 "GNU Wget will use the IP of given network, cURL will use the physical "
 "interface."
 msgstr ""
-"GNU Wget kommer att använda IP-adressen för det angivna nätverket, cURL kommer att använda det fysiska "
-"gränssnittet."
+"GNU Wget kommer att använda IP-adressen för det angivna nätverket, cURL "
+"kommer att använda det fysiska gränssnittet."
 
 msgid "Global Settings"
 msgstr "Globala inställningar"
@@ -277,7 +277,8 @@ msgid "IPv6-Address"
 msgstr "IPv6-adress"
 
 msgid "If both cURL and GNU Wget are installed, Wget is used by default."
-msgstr "Om både cURL och GNU Wget är installerade så används Wget som standard."
+msgstr ""
+"Om både cURL och GNU Wget är installerade så används Wget som standard."
 
 msgid ""
 "If this service section is disabled it could not be started.<br />Neither "
@@ -319,7 +320,9 @@ msgid ""
 msgstr ""
 
 msgid "It is NOT recommended for casual users to change settings on this page."
-msgstr "Det är INTE rekommenderat för vanliga användare att ändra inställningar på den här sidan."
+msgstr ""
+"Det är INTE rekommenderat för vanliga användare att ändra inställningar på "
+"den här sidan."
 
 msgid "Last Update"
 msgstr "Senaste uppdateringen"
@@ -604,7 +607,9 @@ msgid "cURL without Proxy Support"
 msgstr "cURL utan Proxy-stöd"
 
 msgid "can not detect local IP. Please select a different Source combination"
-msgstr "kan inte upptäcka lokal IP-adress. Vänligen välj en annorlunda Käll-kombination"
+msgstr ""
+"kan inte upptäcka lokal IP-adress. Vänligen välj en annorlunda Käll-"
+"kombination"
 
 msgid "can not resolve host:"
 msgstr "kan inte avgöra värd:"
index b567965..c314332 100644 (file)
@@ -29,5 +29,5 @@ msgid ""
 "With this menu you can configure network diagnostics, such as network device "
 "scans and ping tests."
 msgstr ""
-"Med den här menyn så kan du ställa in nätverksdiagnostik så som igenomsökningar och "
-"ping-tester för nätverksenheten."
+"Med den här menyn så kan du ställa in nätverksdiagnostik så som "
+"igenomsökningar och ping-tester för nätverksenheten."
index 925912e..f6a70c3 100644 (file)
@@ -7,7 +7,7 @@ msgstr ""
 "Language-Team: \n"
 "MIME-Version: 1.0\n"
 "Content-Transfer-Encoding: 8bit\n"
-"X-Generator: Poedit 2.0.2\n"
+"X-Generator: Poedit 2.0.3\n"
 "Last-Translator: INAGAKI Hiroshi <musashino.open@gmail.com>\n"
 "Plural-Forms: nplurals=1; plural=0;\n"
 "Language: ja\n"
@@ -47,7 +47,7 @@ msgid "Check routing table"
 msgstr "ルーティング テーブルのチェック"
 
 msgid "Collecting data..."
-msgstr ""
+msgstr "データ収集中です..."
 
 msgid "Configuration"
 msgstr "設定"
@@ -77,7 +77,7 @@ msgid "Diagnostics"
 msgstr "診断機能"
 
 msgid "Disabled"
-msgstr ""
+msgstr "無効"
 
 msgid ""
 "Downed interface will be deemed up after this many successful ping tests"
@@ -89,7 +89,7 @@ msgid "Enabled"
 msgstr "有効"
 
 msgid "Error collecting troubleshooting information"
-msgstr ""
+msgstr "トラブルシューティング情報の収集エラー"
 
 msgid "Errors"
 msgstr "エラー"
@@ -145,7 +145,7 @@ msgid "Internet Protocol"
 msgstr "インターネット プロトコル"
 
 msgid "Last 50 MWAN systemlog entries. Newest entries sorted at the top :"
-msgstr ""
+msgstr "直近の MWAN システムログ(50行)です。一番上が最新の行です:"
 
 msgid "Last resort"
 msgstr "最終手段"
@@ -222,7 +222,7 @@ msgstr ""
 "(例: \"1024:2048\")を、クオーテーション無しで指定することができます。"
 
 msgid "Member"
-msgstr ""
+msgstr "メンバー"
 
 msgid "Member used"
 msgstr "使用されるメンバー"
@@ -260,28 +260,28 @@ msgid "No"
 msgstr "いいえ"
 
 msgid "No MWAN interfaces found"
-msgstr ""
+msgstr "MWAN インターフェースが見つかりません"
 
 msgid "No MWAN systemlog history found"
-msgstr ""
+msgstr "MWAN システムログの履歴が見つかりません"
 
 msgid "No detailed status information available"
-msgstr ""
+msgstr "詳細ステータス情報は利用できません"
 
 msgid "No diagnostic results returned"
-msgstr ""
+msgstr "診断結果がありません"
 
 msgid "No protocol specified"
-msgstr ""
+msgstr "プロトコルが設定されていません"
 
 msgid "Offline"
-msgstr ""
+msgstr "オフライン"
 
 msgid "Online (tracking active)"
-msgstr ""
+msgstr "オンライン(追跡実行中)"
 
 msgid "Online (tracking off)"
-msgstr ""
+msgstr "オンライン(追跡オフ)"
 
 msgid "Overview"
 msgstr "概要"
@@ -332,7 +332,7 @@ msgstr ""
 "ターフェースやメンバー、ルールと同じ名前を使用することはできません。"
 
 msgid "Policy"
-msgstr ""
+msgstr "ポリシー"
 
 msgid "Policy assigned"
 msgstr "アサイン済みポリシー"
@@ -353,7 +353,7 @@ msgid "Restore..."
 msgstr "復元..."
 
 msgid "Rule"
-msgstr ""
+msgstr "ルール"
 
 msgid "Rules"
 msgstr "ルール"
@@ -575,10 +575,10 @@ msgstr ""
 "いません!プロトコルを指定し直してください!"
 
 msgid "Waiting for MWAN to %s..."
-msgstr ""
+msgstr "MWAN の %s を待っています..."
 
 msgid "Waiting for diagnostic results..."
-msgstr ""
+msgstr "診断結果を待っています..."
 
 msgid "Weight"
 msgstr "ウエイト"
@@ -614,13 +614,13 @@ msgid "never"
 msgstr "never"
 
 msgid "restart"
-msgstr ""
+msgstr "再起動"
 
 msgid "start"
-msgstr ""
+msgstr "起動"
 
 msgid "stop"
-msgstr ""
+msgstr "停止"
 
 msgid "unreachable (reject)"
 msgstr "unreachable (reject)"
diff --git a/applications/luci-app-nlbwmon/Makefile b/applications/luci-app-nlbwmon/Makefile
new file mode 100644 (file)
index 0000000..a00177f
--- /dev/null
@@ -0,0 +1,8 @@
+include $(TOPDIR)/rules.mk
+
+LUCI_TITLE:=Netlink based bandwidth accounting
+LUCI_DEPENDS:=+nlbwmon
+
+include ../../luci.mk
+
+# call BuildPackage - OpenWrt buildroot signature
diff --git a/applications/luci-app-nlbwmon/htdocs/luci-static/resources/nlbw.chart.min.js b/applications/luci-app-nlbwmon/htdocs/luci-static/resources/nlbw.chart.min.js
new file mode 100644 (file)
index 0000000..34e3026
--- /dev/null
@@ -0,0 +1,68 @@
+(function(){var p=this,l=p.Chart,e=function(a){this.canvas=a.canvas;this.ctx=a;var b=function(a,b){return a["offset"+b]?a["offset"+b]:document.defaultView.getComputedStyle(a).getPropertyValue(b)};this.width=b(a.canvas,"Width")||a.canvas.width;this.height=b(a.canvas,"Height")||a.canvas.height;this.width=a.canvas.width;this.height=a.canvas.height;this.aspectRatio=this.width/this.height;d.retinaScale(this);return this};e.defaults={global:{animation:!0,animationSteps:60,animationEasing:"easeOutQuart",
+showScale:!0,scaleOverride:!1,scaleSteps:null,scaleStepWidth:null,scaleStartValue:null,scaleLineColor:"rgba(0,0,0,.1)",scaleLineWidth:1,scaleShowLabels:!0,scaleLabel:"<%=value%>",scaleIntegersOnly:!0,scaleBeginAtZero:!1,scaleFontFamily:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",scaleFontSize:12,scaleFontStyle:"normal",scaleFontColor:"#666",responsive:!1,maintainAspectRatio:!0,showTooltips:!0,customTooltips:!1,tooltipEvents:["mousemove","touchstart","touchmove","mouseout"],tooltipFillColor:"rgba(0,0,0,0.8)",
+tooltipFontFamily:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",tooltipFontSize:14,tooltipFontStyle:"normal",tooltipFontColor:"#fff",tooltipTitleFontFamily:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",tooltipTitleFontSize:14,tooltipTitleFontStyle:"bold",tooltipTitleFontColor:"#fff",tooltipTitleTemplate:"<%= label%>",tooltipYPadding:6,tooltipXPadding:6,tooltipCaretSize:8,tooltipCornerRadius:6,tooltipXOffset:10,tooltipTemplate:"<%if (label){%><%=label%>: <%}%><%= value %>",multiTooltipTemplate:"<%= value %>",
+multiTooltipKeyBackground:"#fff",segmentColorDefault:"#A6CEE3 #1F78B4 #B2DF8A #33A02C #FB9A99 #E31A1C #FDBF6F #FF7F00 #CAB2D6 #6A3D9A #B4B482 #B15928".split(" "),segmentHighlightColorDefaults:"#CEF6FF #47A0DC #DAFFB2 #5BC854 #FFC2C1 #FF4244 #FFE797 #FFA728 #F2DAFE #9265C2 #DCDCAA #D98150".split(" "),onAnimationProgress:function(){},onAnimationComplete:function(){}}};e.types={};var d=e.helpers={},k=d.each=function(a,b,c){var f=Array.prototype.slice.call(arguments,3);if(a)if(a.length===+a.length){var d;
+for(d=0;d<a.length;d++)b.apply(c,[a[d],d].concat(f))}else for(d in a)b.apply(c,[a[d],d].concat(f))},h=d.clone=function(a){var b={};k(a,function(c,f){a.hasOwnProperty(f)&&(b[f]=c)});return b},r=d.extend=function(a){k(Array.prototype.slice.call(arguments,1),function(b){k(b,function(c,f){b.hasOwnProperty(f)&&(a[f]=c)})});return a},I=d.merge=function(a,b){var c=Array.prototype.slice.call(arguments,0);c.unshift({});return r.apply(null,c)},J=d.indexOf=function(a,b){if(Array.prototype.indexOf)return a.indexOf(b);
+for(var c=0;c<a.length;c++)if(a[c]===b)return c;return-1};d.where=function(a,b){var c=[];d.each(a,function(a){b(a)&&c.push(a)});return c};d.findNextWhere=function(a,b,c){c||(c=-1);for(c+=1;c<a.length;c++){var f=a[c];if(b(f))return f}};d.findPreviousWhere=function(a,b,c){c||(c=a.length);for(--c;0<=c;c--){var f=a[c];if(b(f))return f}};var D=d.inherits=function(a){var b=this,c=a&&a.hasOwnProperty("constructor")?a.constructor:function(){return b.apply(this,arguments)},f=function(){this.constructor=c};
+f.prototype=b.prototype;c.prototype=new f;c.extend=D;a&&r(c.prototype,a);c.__super__=b.prototype;return c},A=d.noop=function(){},K=d.uid=function(){var a=0;return function(){return"chart-"+a++}}(),L=d.warn=function(a){window.console&&"function"===typeof window.console.warn&&console.warn(a)},M=d.amd="function"===typeof define&&define.amd,u=d.isNumber=function(a){return!isNaN(parseFloat(a))&&isFinite(a)},y=d.max=function(a){return Math.max.apply(Math,a)},w=d.min=function(a){return Math.min.apply(Math,
+a)};d.cap=function(a,b,c){if(u(b)){if(a>b)return b}else if(u(c)&&a<c)return c;return a};var E=d.getDecimalPlaces=function(a){if(0!==a%1&&u(a)){a=a.toString();if(0>a.indexOf("e-"))return a.split(".")[1].length;if(0>a.indexOf("."))return parseInt(a.split("e-")[1]);a=a.split(".")[1].split("e-");return a[0].length+parseInt(a[1])}return 0},B=d.radians=function(a){return Math.PI/180*a};d.getAngleFromPoint=function(a,b){var c=b.x-a.x,f=b.y-a.y,d=Math.sqrt(c*c+f*f),m=2*Math.PI+Math.atan2(f,c);0>c&&0>f&&(m+=
+2*Math.PI);return{angle:m,distance:d}};var F=d.aliasPixel=function(a){return 0===a%2?0:.5};d.splineCurve=function(a,b,c,f){var d=Math.sqrt(Math.pow(b.x-a.x,2)+Math.pow(b.y-a.y,2)),m=Math.sqrt(Math.pow(c.x-b.x,2)+Math.pow(c.y-b.y,2)),g=f*d/(d+m);f=f*m/(d+m);return{inner:{x:b.x-g*(c.x-a.x),y:b.y-g*(c.y-a.y)},outer:{x:b.x+f*(c.x-a.x),y:b.y+f*(c.y-a.y)}}};var N=d.calculateOrderOfMagnitude=function(a){return Math.floor(Math.log(a)/Math.LN10)};d.calculateScaleRange=function(a,b,c,f,d){b=Math.floor(b/(1.5*
+c));c=2>=b;var m=[];k(a,function(a){null==a||m.push(a)});var g=w(m),e=y(m);e===g&&(e+=.5,.5<=g&&!f?g-=.5:e+=.5);a=N(Math.abs(e-g));f=f?0:Math.floor(g/(1*Math.pow(10,a)))*Math.pow(10,a);for(var e=Math.ceil(e/(1*Math.pow(10,a)))*Math.pow(10,a)-f,g=Math.pow(10,a),n=Math.round(e/g);(n>b||2*n<b)&&!c;)if(n>b)g*=2,n=Math.round(e/g),0!==n%1&&(c=!0);else if(d&&0<=a)if(0===g/2%1)g/=2,n=Math.round(e/g);else break;else g/=2,n=Math.round(e/g);c&&(n=2,g=e/n);return{steps:n,stepValue:g,min:f,max:f+n*g}};var t=d.template=
+function(a,b){if(a instanceof Function)return a(b);var c={},c=/\W/.test(a)?new Function("obj","var p=[],print=function(){p.push.apply(p,arguments);};with(obj){p.push('"+a.replace(/[\r\t\n]/g," ").split("<%").join("\t").replace(/((^|%>)[^\t]*)'/g,"$1\r").replace(/\t=(.*?)%>/g,"',$1,'").split("\t").join("');").split("%>").join("p.push('").split("\r").join("\\'")+"');}return p.join('');"):c[a]=c[a];return b?c(b):c};d.generateLabels=function(a,b,c,f){var d=Array(b);a&&k(d,function(b,e){d[e]=t(a,{value:c+
+f*(e+1)})});return d};var x=d.easingEffects={linear:function(a){return a},easeInQuad:function(a){return a*a},easeOutQuad:function(a){return-1*a*(a-2)},easeInOutQuad:function(a){return 1>(a/=.5)?.5*a*a:-.5*(--a*(a-2)-1)},easeInCubic:function(a){return a*a*a},easeOutCubic:function(a){return 1*((a=a/1-1)*a*a+1)},easeInOutCubic:function(a){return 1>(a/=.5)?.5*a*a*a:.5*((a-=2)*a*a+2)},easeInQuart:function(a){return a*a*a*a},easeOutQuart:function(a){return-1*((a=a/1-1)*a*a*a-1)},easeInOutQuart:function(a){return 1>
+(a/=.5)?.5*a*a*a*a:-.5*((a-=2)*a*a*a-2)},easeInQuint:function(a){return 1*(a/=1)*a*a*a*a},easeOutQuint:function(a){return 1*((a=a/1-1)*a*a*a*a+1)},easeInOutQuint:function(a){return 1>(a/=.5)?.5*a*a*a*a*a:.5*((a-=2)*a*a*a*a+2)},easeInSine:function(a){return-1*Math.cos(a/1*(Math.PI/2))+1},easeOutSine:function(a){return 1*Math.sin(a/1*(Math.PI/2))},easeInOutSine:function(a){return-.5*(Math.cos(Math.PI*a/1)-1)},easeInExpo:function(a){return 0===a?1:1*Math.pow(2,10*(a/1-1))},easeOutExpo:function(a){return 1===
+a?1:1*(-Math.pow(2,-10*a/1)+1)},easeInOutExpo:function(a){return 0===a?0:1===a?1:1>(a/=.5)?.5*Math.pow(2,10*(a-1)):.5*(-Math.pow(2,-10*--a)+2)},easeInCirc:function(a){return 1<=a?a:-1*(Math.sqrt(1-(a/=1)*a)-1)},easeOutCirc:function(a){return 1*Math.sqrt(1-(a=a/1-1)*a)},easeInOutCirc:function(a){return 1>(a/=.5)?-.5*(Math.sqrt(1-a*a)-1):.5*(Math.sqrt(1-(a-=2)*a)+1)},easeInElastic:function(a){var b=1.70158,c=0,f=1;if(0===a)return 0;if(1==(a/=1))return 1;c||(c=.3);f<Math.abs(1)?(f=1,b=c/4):b=c/(2*Math.PI)*
+Math.asin(1/f);return-(f*Math.pow(2,10*--a)*Math.sin(2*(1*a-b)*Math.PI/c))},easeOutElastic:function(a){var b=1.70158,c=0,f=1;if(0===a)return 0;if(1==(a/=1))return 1;c||(c=.3);f<Math.abs(1)?(f=1,b=c/4):b=c/(2*Math.PI)*Math.asin(1/f);return f*Math.pow(2,-10*a)*Math.sin(2*(1*a-b)*Math.PI/c)+1},easeInOutElastic:function(a){var b=1.70158,c=0,f=1;if(0===a)return 0;if(2==(a/=.5))return 1;c||(c=.3*1.5);f<Math.abs(1)?(f=1,b=c/4):b=c/(2*Math.PI)*Math.asin(1/f);return 1>a?-.5*f*Math.pow(2,10*--a)*Math.sin(2*
+(1*a-b)*Math.PI/c):f*Math.pow(2,-10*--a)*Math.sin(2*(1*a-b)*Math.PI/c)*.5+1},easeInBack:function(a){return 1*(a/=1)*a*(2.70158*a-1.70158)},easeOutBack:function(a){return 1*((a=a/1-1)*a*(2.70158*a+1.70158)+1)},easeInOutBack:function(a){var b=1.70158;return 1>(a/=.5)?.5*a*a*(((b*=1.525)+1)*a-b):.5*((a-=2)*a*(((b*=1.525)+1)*a+b)+2)},easeInBounce:function(a){return 1-x.easeOutBounce(1-a)},easeOutBounce:function(a){return(a/=1)<1/2.75?7.5625*a*a:a<2/2.75?1*(7.5625*(a-=1.5/2.75)*a+.75):a<2.5/2.75?1*(7.5625*
+(a-=2.25/2.75)*a+.9375):1*(7.5625*(a-=2.625/2.75)*a+.984375)},easeInOutBounce:function(a){return.5>a?.5*x.easeInBounce(2*a):.5*x.easeOutBounce(2*a-1)+.5}},G=d.requestAnimFrame=function(){return window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(a){return window.setTimeout(a,1E3/60)}}();d.cancelAnimFrame=function(){return window.cancelAnimationFrame||window.webkitCancelAnimationFrame||
+window.mozCancelAnimationFrame||window.oCancelAnimationFrame||window.msCancelAnimationFrame||function(a){return window.clearTimeout(a,1E3/60)}}();d.animationLoop=function(a,b,c,f,d,e){var g=0,k=x[c]||x.linear,n=function(){g++;var c=g/b,h=k(c);a.call(e,h,c,g);f.call(e,h,c);g<b?e.animationFrame=G(n):d.apply(e)};G(n)};d.getRelativePosition=function(a){var b;b=a.originalEvent||a;var c=(a.currentTarget||a.srcElement).getBoundingClientRect();b.touches?(a=b.touches[0].clientX-c.left,b=b.touches[0].clientY-
+c.top):(a=b.clientX-c.left,b=b.clientY-c.top);return{x:a,y:b}};var O=d.addEvent=function(a,b,c){a.addEventListener?a.addEventListener(b,c):a.attachEvent?a.attachEvent("on"+b,c):a["on"+b]=c},P=d.removeEvent=function(a,b,c){a.removeEventListener?a.removeEventListener(b,c,!1):a.detachEvent?a.detachEvent("on"+b,c):a["on"+b]=A};d.bindEvents=function(a,b,c){a.events||(a.events={});k(b,function(b){a.events[b]=function(){c.apply(a,arguments)};O(a.chart.canvas,b,a.events[b])})};var Q=d.unbindEvents=function(a,
+b){k(b,function(b,f){P(a.chart.canvas,f,b)})},R=d.getMaximumWidth=function(a){a=a.parentNode;var b=parseInt(z(a,"padding-left"))+parseInt(z(a,"padding-right"));return a?a.clientWidth-b:0},S=d.getMaximumHeight=function(a){a=a.parentNode;var b=parseInt(z(a,"padding-bottom"))+parseInt(z(a,"padding-top"));return a?a.clientHeight-b:0},z=d.getStyle=function(a,b){return a.currentStyle?a.currentStyle[b]:document.defaultView.getComputedStyle(a,null).getPropertyValue(b)};d.getMaximumSize=d.getMaximumWidth;
+var T=d.retinaScale=function(a){var b=a.ctx,c=a.canvas.width;a=a.canvas.height;window.devicePixelRatio&&(b.canvas.style.width=c+"px",b.canvas.style.height=a+"px",b.canvas.height=a*window.devicePixelRatio,b.canvas.width=c*window.devicePixelRatio,b.scale(window.devicePixelRatio,window.devicePixelRatio))},U=d.clear=function(a){a.ctx.clearRect(0,0,a.width,a.height)},v=d.fontString=function(a,b,c){return b+" "+a+"px "+c},C=d.longestText=function(a,b,c){a.font=b;var f=0;k(c,function(b){b=a.measureText(b).width;
+f=b>f?b:f});return f},H=d.drawRoundedRectangle=function(a,b,c,f,d,e){a.beginPath();a.moveTo(b+e,c);a.lineTo(b+f-e,c);a.quadraticCurveTo(b+f,c,b+f,c+e);a.lineTo(b+f,c+d-e);a.quadraticCurveTo(b+f,c+d,b+f-e,c+d);a.lineTo(b+e,c+d);a.quadraticCurveTo(b,c+d,b,c+d-e);a.lineTo(b,c+e);a.quadraticCurveTo(b,c,b+e,c);a.closePath()};e.instances={};e.Type=function(a,b,c){this.options=b;this.chart=c;this.id=K();e.instances[this.id]=this;b.responsive&&this.resize();this.initialize.call(this,a)};r(e.Type.prototype,
+{initialize:function(){return this},clear:function(){U(this.chart);return this},stop:function(){e.animationService.cancelAnimation(this);return this},resize:function(a){this.stop();var b=this.chart.canvas,c=R(this.chart.canvas),f=this.options.maintainAspectRatio?c/this.chart.aspectRatio:S(this.chart.canvas);b.width=this.chart.width=c;b.height=this.chart.height=f;T(this.chart);"function"===typeof a&&a.apply(this,Array.prototype.slice.call(arguments,1));return this},reflow:A,render:function(a){a&&this.reflow();
+this.options.animation&&!a?(a=new e.Animation,a.numSteps=this.options.animationSteps,a.easing=this.options.animationEasing,a.render=function(a,c){var f=c.currentStep/c.numSteps,e=(0,d.easingEffects[c.easing])(f);a.draw(e,f,c.currentStep)},a.onAnimationProgress=this.options.onAnimationProgress,a.onAnimationComplete=this.options.onAnimationComplete,e.animationService.addAnimation(this,a)):(this.draw(),this.options.onAnimationComplete.call(this));return this},generateLegend:function(){return t(this.options.legendTemplate,
+this)},destroy:function(){this.clear();Q(this,this.events);var a=this.chart.canvas;a.width=this.chart.width;a.height=this.chart.height;a.style.removeProperty?(a.style.removeProperty("width"),a.style.removeProperty("height")):(a.style.removeAttribute("width"),a.style.removeAttribute("height"));delete e.instances[this.id]},showTooltip:function(a,b){"undefined"===typeof this.activeElements&&(this.activeElements=[]);if(function(a){var b=!1;if(a.length!==this.activeElements.length)return b=!0;k(a,function(a,
+c){a!==this.activeElements[c]&&(b=!0)},this);return b}.call(this,a)||b){this.activeElements=a;this.draw();this.options.customTooltips&&this.options.customTooltips(!1);if(0<a.length)if(this.datasets&&1<this.datasets.length){for(var c,f,q=this.datasets.length-1;0<=q&&(c=this.datasets[q].points||this.datasets[q].bars||this.datasets[q].segments,f=J(c,a[0]),-1===f);q--);var m=[],g=[];c=function(a){var b=[],c,e=[],q=[],k,h,l;d.each(this.datasets,function(a){c=a.points||a.bars||a.segments;c[f]&&c[f].hasValue()&&
+b.push(c[f])});d.each(b,function(a){e.push(a.x);q.push(a.y);m.push(d.template(this.options.multiTooltipTemplate,a));g.push({fill:a._saved.fillColor||a.fillColor,stroke:a._saved.strokeColor||a.strokeColor})},this);l=w(q);k=y(q);h=w(e);a=y(e);return{x:h>this.chart.width/2?h:a,y:(l+k)/2}}.call(this,f);(new e.MultiTooltip({x:c.x,y:c.y,xPadding:this.options.tooltipXPadding,yPadding:this.options.tooltipYPadding,xOffset:this.options.tooltipXOffset,fillColor:this.options.tooltipFillColor,textColor:this.options.tooltipFontColor,
+fontFamily:this.options.tooltipFontFamily,fontStyle:this.options.tooltipFontStyle,fontSize:this.options.tooltipFontSize,titleTextColor:this.options.tooltipTitleFontColor,titleFontFamily:this.options.tooltipTitleFontFamily,titleFontStyle:this.options.tooltipTitleFontStyle,titleFontSize:this.options.tooltipTitleFontSize,cornerRadius:this.options.tooltipCornerRadius,labels:m,legendColors:g,legendColorBackground:this.options.multiTooltipKeyBackground,title:t(this.options.tooltipTitleTemplate,a[0]),chart:this.chart,
+ctx:this.chart.ctx,custom:this.options.customTooltips})).draw()}else k(a,function(a){var b=a.tooltipPosition();(new e.Tooltip({x:Math.round(b.x),y:Math.round(b.y),xPadding:this.options.tooltipXPadding,yPadding:this.options.tooltipYPadding,fillColor:this.options.tooltipFillColor,textColor:this.options.tooltipFontColor,fontFamily:this.options.tooltipFontFamily,fontStyle:this.options.tooltipFontStyle,fontSize:this.options.tooltipFontSize,caretHeight:this.options.tooltipCaretSize,cornerRadius:this.options.tooltipCornerRadius,
+text:t(this.options.tooltipTemplate,a),chart:this.chart,custom:this.options.customTooltips})).draw()},this);return this}},toBase64Image:function(){return this.chart.canvas.toDataURL.apply(this.chart.canvas,arguments)}});e.Type.extend=function(a){var b=this,c=function(){return b.apply(this,arguments)};c.prototype=h(b.prototype);r(c.prototype,a);c.extend=e.Type.extend;if(a.name||b.prototype.name){var f=a.name||b.prototype.name,d=e.defaults[b.prototype.name]?h(e.defaults[b.prototype.name]):{};e.defaults[f]=
+r(d,a.defaults);e.types[f]=c;e.prototype[f]=function(a,b){var d=I(e.defaults.global,e.defaults[f],b||{});return new c(a,d,this)}}else L("Name not provided for this chart, so it hasn't been registered");return b};e.Element=function(a){r(this,a);this.initialize.apply(this,arguments);this.save()};r(e.Element.prototype,{initialize:function(){},restore:function(a){a?k(a,function(a){this[a]=this._saved[a]},this):r(this,this._saved);return this},save:function(){this._saved=h(this);delete this._saved._saved;
+return this},update:function(a){k(a,function(a,c){this._saved[c]=this[c];this[c]=a},this);return this},transition:function(a,b){k(a,function(a,f){this[f]=(a-this._saved[f])*b+this._saved[f]},this);return this},tooltipPosition:function(){return{x:this.x,y:this.y}},hasValue:function(){return u(this.value)}});e.Element.extend=D;e.Point=e.Element.extend({display:!0,inRange:function(a,b){return Math.pow(a-this.x,2)+Math.pow(b-this.y,2)<Math.pow(this.hitDetectionRadius+this.radius,2)},draw:function(){if(this.display){var a=
+this.ctx;a.beginPath();a.arc(this.x,this.y,this.radius,0,2*Math.PI);a.closePath();a.strokeStyle=this.strokeColor;a.lineWidth=this.strokeWidth;a.fillStyle=this.fillColor;a.fill();a.stroke()}}});e.Arc=e.Element.extend({inRange:function(a,b){var c=d.getAngleFromPoint(this,{x:a,y:b}),f=c.angle%(2*Math.PI),e=(2*Math.PI+this.startAngle)%(2*Math.PI),m=(2*Math.PI+this.endAngle)%(2*Math.PI)||360,c=c.distance>=this.innerRadius&&c.distance<=this.outerRadius;return(m<e?f<=m||f>=e:f>=e&&f<=m)&&c},tooltipPosition:function(){var a=
+this.startAngle+(this.endAngle-this.startAngle)/2,b=(this.outerRadius-this.innerRadius)/2+this.innerRadius;return{x:this.x+Math.cos(a)*b,y:this.y+Math.sin(a)*b}},draw:function(a){a=this.ctx;a.beginPath();a.arc(this.x,this.y,0>this.outerRadius?0:this.outerRadius,this.startAngle,this.endAngle);a.arc(this.x,this.y,0>this.innerRadius?0:this.innerRadius,this.endAngle,this.startAngle,!0);a.closePath();a.strokeStyle=this.strokeColor;a.lineWidth=this.strokeWidth;a.fillStyle=this.fillColor;a.fill();a.lineJoin=
+"bevel";this.showStroke&&a.stroke()}});e.Rectangle=e.Element.extend({draw:function(){var a=this.ctx,b=this.width/2,c=this.x-b,b=this.x+b,f=this.base-(this.base-this.y),d=this.strokeWidth/2;this.showStroke&&(c+=d,b-=d,f+=d);a.beginPath();a.fillStyle=this.fillColor;a.strokeStyle=this.strokeColor;a.lineWidth=this.strokeWidth;a.moveTo(c,this.base);a.lineTo(c,f);a.lineTo(b,f);a.lineTo(b,this.base);a.fill();this.showStroke&&a.stroke()},height:function(){return this.base-this.y},inRange:function(a,b){return a>=
+this.x-this.width/2&&a<=this.x+this.width/2&&b>=this.y&&b<=this.base}});e.Animation=e.Element.extend({currentStep:null,numSteps:60,easing:"",render:null,onAnimationProgress:null,onAnimationComplete:null});e.Tooltip=e.Element.extend({draw:function(){var a=this.chart.ctx;a.font=v(this.fontSize,this.fontStyle,this.fontFamily);this.xAlign="center";this.yAlign="above";var b=this.caretPadding=2,c=a.measureText(this.text).width+2*this.xPadding,f=this.fontSize+2*this.yPadding,d=f+this.caretHeight+b;this.x+
+c/2>this.chart.width?this.xAlign="left":0>this.x-c/2&&(this.xAlign="right");0>this.y-d&&(this.yAlign="below");var e=this.x-c/2,d=this.y-d;a.fillStyle=this.fillColor;if(this.custom)this.custom(this);else{switch(this.yAlign){case "above":a.beginPath();a.moveTo(this.x,this.y-b);a.lineTo(this.x+this.caretHeight,this.y-(b+this.caretHeight));a.lineTo(this.x-this.caretHeight,this.y-(b+this.caretHeight));a.closePath();a.fill();break;case "below":d=this.y+b+this.caretHeight,a.beginPath(),a.moveTo(this.x,this.y+
+b),a.lineTo(this.x+this.caretHeight,this.y+b+this.caretHeight),a.lineTo(this.x-this.caretHeight,this.y+b+this.caretHeight),a.closePath(),a.fill()}switch(this.xAlign){case "left":e=this.x-c+(this.cornerRadius+this.caretHeight);break;case "right":e=this.x-(this.cornerRadius+this.caretHeight)}H(a,e,d,c,f,this.cornerRadius);a.fill();a.fillStyle=this.textColor;a.textAlign="center";a.textBaseline="middle";a.fillText(this.text,e+c/2,d+f/2)}}});e.MultiTooltip=e.Element.extend({initialize:function(){this.font=
+v(this.fontSize,this.fontStyle,this.fontFamily);this.titleFont=v(this.titleFontSize,this.titleFontStyle,this.titleFontFamily);this.titleHeight=this.title?1.5*this.titleFontSize:0;this.height=this.labels.length*this.fontSize+this.fontSize/2*(this.labels.length-1)+2*this.yPadding+this.titleHeight;this.ctx.font=this.titleFont;var a=this.ctx.measureText(this.title).width,b=C(this.ctx,this.font,this.labels)+this.fontSize+3;this.width=y([b,a])+2*this.xPadding;a=this.height/2;0>this.y-a?this.y=a:this.y+
+a>this.chart.height&&(this.y=this.chart.height-a);this.x=this.x>this.chart.width/2?this.x-(this.xOffset+this.width):this.x+this.xOffset},getLineHeight:function(a){var b=this.y-this.height/2+this.yPadding;return 0===a?b+this.titleHeight/3:b+(1.5*this.fontSize*(a-1)+this.fontSize/2)+this.titleHeight},draw:function(){if(this.custom)this.custom(this);else{H(this.ctx,this.x,this.y-this.height/2,this.width,this.height,this.cornerRadius);var a=this.ctx;a.fillStyle=this.fillColor;a.fill();a.closePath();a.textAlign=
+"left";a.textBaseline="middle";a.fillStyle=this.titleTextColor;a.font=this.titleFont;a.fillText(this.title,this.x+this.xPadding,this.getLineHeight(0));a.font=this.font;d.each(this.labels,function(b,c){a.fillStyle=this.textColor;a.fillText(b,this.x+this.xPadding+this.fontSize+3,this.getLineHeight(c+1));a.fillStyle=this.legendColorBackground;a.fillRect(this.x+this.xPadding,this.getLineHeight(c+1)-this.fontSize/2,this.fontSize,this.fontSize);a.fillStyle=this.legendColors[c].fill;a.fillRect(this.x+this.xPadding,
+this.getLineHeight(c+1)-this.fontSize/2,this.fontSize,this.fontSize)},this)}}});e.Scale=e.Element.extend({initialize:function(){this.fit()},buildYLabels:function(){this.yLabels=[];for(var a=E(this.stepValue),b=0;b<=this.steps;b++)this.yLabels.push(t(this.templateString,{value:(this.min+b*this.stepValue).toFixed(a)}));this.yLabelWidth=this.display&&this.showLabels?C(this.ctx,this.font,this.yLabels)+10:0},addXLabel:function(a){this.xLabels.push(a);this.valuesCount++;this.fit()},removeXLabel:function(){this.xLabels.shift();
+this.valuesCount--;this.fit()},fit:function(){this.startPoint=this.display?this.fontSize:0;this.endPoint=this.display?this.height-1.5*this.fontSize-5:this.height;this.startPoint+=this.padding;var a=this.endPoint-=this.padding,b=this.endPoint-this.startPoint,c;this.calculateYRange(b);this.buildYLabels();for(this.calculateXLabelRotation();b>this.endPoint-this.startPoint;)b=this.endPoint-this.startPoint,c=this.yLabelWidth,this.calculateYRange(b),this.buildYLabels(),c<this.yLabelWidth&&(this.endPoint=
+a,this.calculateXLabelRotation())},calculateXLabelRotation:function(){this.ctx.font=this.font;var a=this.ctx.measureText(this.xLabels[0]).width,b;this.xScalePaddingRight=this.ctx.measureText(this.xLabels[this.xLabels.length-1]).width/2+3;this.xScalePaddingLeft=a/2>this.yLabelWidth?a/2:this.yLabelWidth;this.xLabelRotation=0;if(this.display){var c=C(this.ctx,this.font,this.xLabels),f;this.xLabelWidth=c;for(var d=Math.floor(this.calculateX(1)-this.calculateX(0))-6;this.xLabelWidth>d&&0===this.xLabelRotation||
+this.xLabelWidth>d&&90>=this.xLabelRotation&&0<this.xLabelRotation;)f=Math.cos(B(this.xLabelRotation)),b=f*a,b+this.fontSize/2>this.yLabelWidth&&(this.xScalePaddingLeft=b+this.fontSize/2),this.xScalePaddingRight=this.fontSize/2,this.xLabelRotation++,this.xLabelWidth=f*c;0<this.xLabelRotation&&(this.endPoint-=Math.sin(B(this.xLabelRotation))*c+3)}else this.xLabelWidth=0,this.xScalePaddingLeft=this.xScalePaddingRight=this.padding},calculateYRange:A,drawingArea:function(){return this.startPoint-this.endPoint},
+calculateY:function(a){var b=this.drawingArea()/(this.min-this.max);return this.endPoint-b*(a-this.min)},calculateX:function(a){var b=(this.width-(this.xScalePaddingLeft+this.xScalePaddingRight))/Math.max(this.valuesCount-(this.offsetGridLines?0:1),1);a=b*a+this.xScalePaddingLeft;this.offsetGridLines&&(a+=b/2);return Math.round(a)},update:function(a){d.extend(this,a);this.fit()},draw:function(){var a=this.ctx,b=(this.endPoint-this.startPoint)/this.steps,c=Math.round(this.xScalePaddingLeft);this.display&&
+(a.fillStyle=this.textColor,a.font=this.font,k(this.yLabels,function(f,e){var k=this.endPoint-b*e,g=Math.round(k),h=this.showHorizontalLines;a.textAlign="right";a.textBaseline="middle";this.showLabels&&a.fillText(f,c-10,k);0!==e||h||(h=!0);h&&a.beginPath();0<e?(a.lineWidth=this.gridLineWidth,a.strokeStyle=this.gridLineColor):(a.lineWidth=this.lineWidth,a.strokeStyle=this.lineColor);g+=d.aliasPixel(a.lineWidth);h&&(a.moveTo(c,g),a.lineTo(this.width,g),a.stroke(),a.closePath());a.lineWidth=this.lineWidth;
+a.strokeStyle=this.lineColor;a.beginPath();a.moveTo(c-5,g);a.lineTo(c,g);a.stroke();a.closePath()},this),k(this.xLabels,function(b,c){var d=this.calculateX(c)+F(this.lineWidth),e=this.calculateX(c-(this.offsetGridLines?.5:0))+F(this.lineWidth),k=0<this.xLabelRotation,h=this.showVerticalLines;0!==c||h||(h=!0);h&&a.beginPath();0<c?(a.lineWidth=this.gridLineWidth,a.strokeStyle=this.gridLineColor):(a.lineWidth=this.lineWidth,a.strokeStyle=this.lineColor);h&&(a.moveTo(e,this.endPoint),a.lineTo(e,this.startPoint-
+3),a.stroke(),a.closePath());a.lineWidth=this.lineWidth;a.strokeStyle=this.lineColor;a.beginPath();a.moveTo(e,this.endPoint);a.lineTo(e,this.endPoint+5);a.stroke();a.closePath();a.save();a.translate(d,k?this.endPoint+12:this.endPoint+8);a.rotate(-1*B(this.xLabelRotation));a.font=this.font;a.textAlign=k?"right":"center";a.textBaseline=k?"middle":"top";a.fillText(b,0,0);a.restore()},this))}});e.RadialScale=e.Element.extend({initialize:function(){this.size=w([this.height,this.width]);this.drawingArea=
+this.display?this.size/2-(this.fontSize/2+this.backdropPaddingY):this.size/2},calculateCenterOffset:function(a){return this.drawingArea/(this.max-this.min)*(a-this.min)},update:function(){this.lineArc?this.drawingArea=this.display?this.size/2-(this.fontSize/2+this.backdropPaddingY):this.size/2:this.setScaleSize();this.buildYLabels()},buildYLabels:function(){this.yLabels=[];for(var a=E(this.stepValue),b=0;b<=this.steps;b++)this.yLabels.push(t(this.templateString,{value:(this.min+b*this.stepValue).toFixed(a)}))},
+getCircumference:function(){return 2*Math.PI/this.valuesCount},setScaleSize:function(){var a=w([this.height/2-this.pointLabelFontSize-5,this.width/2]),b,c,d,e=this.width,k,g=0,h;this.ctx.font=v(this.pointLabelFontSize,this.pointLabelFontStyle,this.pointLabelFontFamily);for(c=0;c<this.valuesCount;c++)b=this.getPointPosition(c,a),d=this.ctx.measureText(t(this.templateString,{value:this.labels[c]})).width+5,0===c||c===this.valuesCount/2?(d/=2,b.x+d>e&&(e=b.x+d,k=c),b.x-d<g&&(g=b.x-d,h=c)):c<this.valuesCount/
+2?b.x+d>e&&(e=b.x+d,k=c):c>this.valuesCount/2&&b.x-d<g&&(g=b.x-d,h=c);b=g;e=Math.ceil(e-this.width);k=this.getIndexAngle(k);h=this.getIndexAngle(h);k=e/Math.sin(k+Math.PI/2);h=b/Math.sin(h+Math.PI/2);k=u(k)?k:0;h=u(h)?h:0;this.drawingArea=a-(h+k)/2;this.setCenterPoint(h,k)},setCenterPoint:function(a,b){this.xCenter=(a+this.drawingArea+(this.width-b-this.drawingArea))/2;this.yCenter=this.height/2},getIndexAngle:function(a){return 2*Math.PI/this.valuesCount*a-Math.PI/2},getPointPosition:function(a,
+b){var c=this.getIndexAngle(a);return{x:Math.cos(c)*b+this.xCenter,y:Math.sin(c)*b+this.yCenter}},draw:function(){if(this.display){var a=this.ctx;k(this.yLabels,function(b,c){if(0<c){var d=this.drawingArea/this.steps*c,e=this.yCenter-d;if(0<this.lineWidth){a.strokeStyle=this.lineColor;a.lineWidth=this.lineWidth;if(this.lineArc)a.beginPath(),a.arc(this.xCenter,this.yCenter,d,0,2*Math.PI);else{a.beginPath();for(var f=0;f<this.valuesCount;f++)d=this.getPointPosition(f,this.calculateCenterOffset(this.min+
+c*this.stepValue)),0===f?a.moveTo(d.x,d.y):a.lineTo(d.x,d.y)}a.closePath();a.stroke()}this.showLabels&&(a.font=v(this.fontSize,this.fontStyle,this.fontFamily),this.showLabelBackdrop&&(d=a.measureText(b).width,a.fillStyle=this.backdropColor,a.fillRect(this.xCenter-d/2-this.backdropPaddingX,e-this.fontSize/2-this.backdropPaddingY,d+2*this.backdropPaddingX,this.fontSize+2*this.backdropPaddingY)),a.textAlign="center",a.textBaseline="middle",a.fillStyle=this.fontColor,a.fillText(b,this.xCenter,e))}},this);
+if(!this.lineArc){a.lineWidth=this.angleLineWidth;a.strokeStyle=this.angleLineColor;for(var b=this.valuesCount-1;0<=b;b--){var c=null,d=null;0<this.angleLineWidth&&(c=this.calculateCenterOffset(this.max),d=this.getPointPosition(b,c),a.beginPath(),a.moveTo(this.xCenter,this.yCenter),a.lineTo(d.x,d.y),a.stroke(),a.closePath());if(this.backgroundColors&&this.backgroundColors.length==this.valuesCount){null==c&&(c=this.calculateCenterOffset(this.max));null==d&&(d=this.getPointPosition(b,c));var e=this.getPointPosition(0===
+b?this.valuesCount-1:b-1,c),h=this.getPointPosition(b===this.valuesCount-1?0:b+1,c),c=(e.x+d.x)/2,e=(e.y+d.y)/2,g=(d.x+h.x)/2,h=(d.y+h.y)/2;a.beginPath();a.moveTo(this.xCenter,this.yCenter);a.lineTo(c,e);a.lineTo(d.x,d.y);a.lineTo(g,h);a.fillStyle=this.backgroundColors[b];a.fill();a.closePath()}d=this.getPointPosition(b,this.calculateCenterOffset(this.max)+5);a.font=v(this.pointLabelFontSize,this.pointLabelFontStyle,this.pointLabelFontFamily);a.fillStyle=this.pointLabelFontColor;e=this.labels.length;
+c=this.labels.length/2;g=c/2;h=b<g||b>e-g;e=b===g||b===e-g;a.textAlign=0===b?"center":b===c?"center":b<c?"left":"right";a.textBaseline=e?"middle":h?"bottom":"top";a.fillText(this.labels[b],d.x,d.y)}}}}});e.animationService={frameDuration:17,animations:[],dropFrames:0,addAnimation:function(a,b){for(var c=0;c<this.animations.length;++c)if(this.animations[c].chartInstance===a){this.animations[c].animationObject=b;return}this.animations.push({chartInstance:a,animationObject:b});1==this.animations.length&&
+d.requestAnimFrame.call(window,this.digestWrapper)},cancelAnimation:function(a){var b=d.findNextWhere(this.animations,function(b){return b.chartInstance===a});b&&this.animations.splice(b,1)},digestWrapper:function(){e.animationService.startDigest.call(e.animationService)},startDigest:function(){var a=Date.now(),b=0;1<this.dropFrames&&(b=Math.floor(this.dropFrames),this.dropFrames-=b);for(var c=0;c<this.animations.length;c++)null===this.animations[c].animationObject.currentStep&&(this.animations[c].animationObject.currentStep=
+0),this.animations[c].animationObject.currentStep+=1+b,this.animations[c].animationObject.currentStep>this.animations[c].animationObject.numSteps&&(this.animations[c].animationObject.currentStep=this.animations[c].animationObject.numSteps),this.animations[c].animationObject.render(this.animations[c].chartInstance,this.animations[c].animationObject),this.animations[c].animationObject.currentStep==this.animations[c].animationObject.numSteps&&(this.animations[c].animationObject.onAnimationComplete.call(this.animations[c].chartInstance),
+this.animations.splice(c,1),c--);a=(Date.now()-a-this.frameDuration)/this.frameDuration;1<a&&(this.dropFrames+=a);0<this.animations.length&&d.requestAnimFrame.call(window,this.digestWrapper)}};d.addEvent(window,"resize",function(){var a;return function(){clearTimeout(a);a=setTimeout(function(){k(e.instances,function(a){a.options.responsive&&a.resize(a.render,!0)})},50)}}());M?define(function(){return e}):"object"===typeof module&&module.exports&&(module.exports=e);p.Chart=e;e.noConflict=function(){p.Chart=
+l;return e}}).call(this);
+(function(){var p=this.Chart,l=p.helpers,e={segmentShowStroke:!0,segmentStrokeColor:"#fff",segmentStrokeWidth:2,percentageInnerCutout:50,animationSteps:100,animationEasing:"easeOutBounce",animateRotate:!0,animateScale:!1,legendTemplate:'<ul class="<%=name.toLowerCase()%>-legend"><% for (var i=0; i<segments.length; i++){%><li><span style="background-color:<%=segments[i].fillColor%>"><%if(segments[i].label){%><%=segments[i].label%><%}%></span></li><%}%></ul>'};p.Type.extend({name:"Doughnut",defaults:e,
+initialize:function(d){this.segments=[];this.outerRadius=(l.min([this.chart.width,this.chart.height])-this.options.segmentStrokeWidth/2)/2;this.SegmentArc=p.Arc.extend({ctx:this.chart.ctx,x:this.chart.width/2,y:this.chart.height/2});this.options.showTooltips&&l.bindEvents(this,this.options.tooltipEvents,function(d){d="mouseout"!==d.type?this.getSegmentsAtEvent(d):[];l.each(this.segments,function(d){d.restore(["fillColor"])});l.each(d,function(d){d.fillColor=d.highlightColor});this.showTooltip(d)});
+this.calculateTotal(d);l.each(d,function(e,h){e.color||(e.color="hsl("+360*h/d.length+", 100%, 50%)");this.addData(e,h,!0)},this);this.render()},getSegmentsAtEvent:function(d){var e=[],h=l.getRelativePosition(d);l.each(this.segments,function(d){d.inRange(h.x,h.y)&&e.push(d)},this);return e},addData:function(d,e,h){e=void 0!==e?e:this.segments.length;"undefined"===typeof d.color&&(d.color=p.defaults.global.segmentColorDefault[e%p.defaults.global.segmentColorDefault.length],d.highlight=p.defaults.global.segmentHighlightColorDefaults[e%
+p.defaults.global.segmentHighlightColorDefaults.length]);this.segments.splice(e,0,new this.SegmentArc({value:d.value,outerRadius:this.options.animateScale?0:this.outerRadius,innerRadius:this.options.animateScale?0:this.outerRadius/100*this.options.percentageInnerCutout,fillColor:d.color,highlightColor:d.highlight||d.color,showStroke:this.options.segmentShowStroke,strokeWidth:this.options.segmentStrokeWidth,strokeColor:this.options.segmentStrokeColor,startAngle:1.5*Math.PI,circumference:this.options.animateRotate?
+0:this.calculateCircumference(d.value),label:d.label}));h||(this.reflow(),this.update())},calculateCircumference:function(d){return 0<this.total?d/this.total*Math.PI*2:0},calculateTotal:function(d){this.total=0;l.each(d,function(d){this.total+=Math.abs(d.value)},this)},update:function(){this.calculateTotal(this.segments);l.each(this.activeElements,function(d){d.restore(["fillColor"])});l.each(this.segments,function(d){d.save()});this.render()},removeData:function(d){d=l.isNumber(d)?d:this.segments.length-
+1;this.segments.splice(d,1);this.reflow();this.update()},reflow:function(){l.extend(this.SegmentArc.prototype,{x:this.chart.width/2,y:this.chart.height/2});this.outerRadius=(l.min([this.chart.width,this.chart.height])-this.options.segmentStrokeWidth/2)/2;l.each(this.segments,function(d){d.update({outerRadius:this.outerRadius,innerRadius:this.outerRadius/100*this.options.percentageInnerCutout})},this)},draw:function(d){var e=d?d:1;this.clear();l.each(this.segments,function(d,l){d.transition({circumference:this.calculateCircumference(d.value),
+outerRadius:this.outerRadius,innerRadius:this.outerRadius/100*this.options.percentageInnerCutout},e);d.endAngle=d.startAngle+d.circumference;d.draw();0===l&&(d.startAngle=1.5*Math.PI);l<this.segments.length-1&&(this.segments[l+1].startAngle=d.endAngle)},this)}});p.types.Doughnut.extend({name:"Pie",defaults:l.merge(e,{percentageInnerCutout:0})})}).call(this);
diff --git a/applications/luci-app-nlbwmon/luasrc/controller/nlbw.lua b/applications/luci-app-nlbwmon/luasrc/controller/nlbw.lua
new file mode 100644 (file)
index 0000000..bb56bc6
--- /dev/null
@@ -0,0 +1,225 @@
+-- Copyright 2017 Jo-Philipp Wich <jo@mein.io>
+-- Licensed to the public under the Apache License 2.0.
+
+module("luci.controller.nlbw", package.seeall)
+
+function index()
+       entry({"admin", "nlbw"}, firstchild(), _("Bandwidth Monitor"), 80)
+       entry({"admin", "nlbw", "display"}, template("nlbw/display"), _("Display"), 1)
+       entry({"admin", "nlbw", "config"}, cbi("nlbw/config"), _("Configuration"), 2)
+       entry({"admin", "nlbw", "backup"}, template("nlbw/backup"), _("Backup"), 3)
+       entry({"admin", "nlbw", "data"}, call("action_data"), nil, 4)
+       entry({"admin", "nlbw", "list"}, call("action_list"), nil, 5)
+       entry({"admin", "nlbw", "ptr"}, call("action_ptr"), nil, 6).leaf = true
+       entry({"admin", "nlbw", "download"}, call("action_download"), nil, 7)
+       entry({"admin", "nlbw", "restore"}, post("action_restore"), nil, 8)
+       entry({"admin", "nlbw", "commit"}, call("action_commit"), nil, 9)
+end
+
+local function exec(cmd, args, writer)
+       local os = require "os"
+       local nixio = require "nixio"
+
+       local fdi, fdo = nixio.pipe()
+       local pid = nixio.fork()
+
+       if pid > 0 then
+               fdo:close()
+
+               while true do
+                       local buffer = fdi:read(2048)
+                       local wpid, stat, code = nixio.waitpid(pid, "nohang")
+
+                       if writer and buffer and #buffer > 0 then
+                               writer(buffer)
+                       end
+
+                       if wpid and stat == "exited" then
+                               break
+                       end
+               end
+       elseif pid == 0 then
+               nixio.dup(fdo, nixio.stdout)
+               fdi:close()
+               fdo:close()
+               nixio.exece(cmd, args, nil)
+               nixio.stdout:close()
+               os.exit(1)
+       end
+end
+
+function action_data()
+       local http = require "luci.http"
+
+       local types = {
+               csv = "text/csv",
+               json = "application/json"
+       }
+
+       local args = { }
+       local mtype = http.formvalue("type") or "json"
+       local delim = http.formvalue("delim") or ";"
+       local period = http.formvalue("period")
+       local group_by = http.formvalue("group_by")
+       local order_by = http.formvalue("order_by")
+
+       if types[mtype] then
+               args[#args+1] = "-c"
+               args[#args+1] = mtype
+       else
+               http.status(400, "Unsupported type")
+               return
+       end
+
+       if delim and #delim > 0 then
+               args[#args+1] = "-s%s" % delim
+       end
+
+       if period and #period > 0 then
+               args[#args+1] = "-t"
+               args[#args+1] = period
+       end
+
+       if group_by and #group_by > 0 then
+               args[#args+1] = "-g"
+               args[#args+1] = group_by
+       end
+
+       if order_by and #order_by > 0 then
+               args[#args+1] = "-o"
+               args[#args+1] = order_by
+       end
+
+       http.prepare_content(types[mtype])
+       exec("/usr/sbin/nlbw", args, http.write)
+end
+
+function action_list()
+       local http = require "luci.http"
+
+       local fd = io.popen("/usr/sbin/nlbw -c list")
+       local periods = { }
+
+       if fd then
+               while true do
+                       local period = fd:read("*l")
+
+                       if not period then
+                               break
+                       end
+
+                       periods[#periods+1] = period
+               end
+
+               fd:close()
+       end
+
+       http.prepare_content("application/json")
+       http.write_json(periods)
+end
+
+function action_ptr(...)
+       local http = require "luci.http"
+       local util = require "luci.util"
+
+       http.prepare_content("application/json")
+       http.write_json(util.ubus("network.rrdns", "lookup", {
+               addrs = {...}, timeout = 3000
+       }))
+end
+
+function action_download()
+       local nixio = require "nixio"
+       local http = require "luci.http"
+       local sys = require "luci.sys"
+       local uci = require "luci.model.uci".cursor()
+
+       local dir = uci:get_first("nlbwmon", "nlbwmon", "database_directory")
+               or "/var/lib/nlbwmon"
+
+       if dir and nixio.fs.stat(dir, "type") == "dir" then
+               local n = "nlbwmon-backup-%s-%s.tar.gz"
+                       %{ sys.hostname(), os.date("%Y-%m-%d") }
+
+               http.prepare_content("application/octet-stream")
+               http.header("Content-Disposition", "attachment; filename=\"%s\"" % n)
+               exec("/bin/tar", { "-C", dir, "-c", "-z", ".", "-f", "-" }, http.write)
+       else
+               http.status(500, "Unable to find database directory")
+       end
+end
+
+function action_restore()
+       local nixio = require "nixio"
+       local http = require "luci.http"
+       local i18n = require "luci.i18n"
+       local tpl = require "luci.template"
+       local uci = require "luci.model.uci".cursor()
+
+       local tmp = "/tmp/nlbw-restore.tar.gz"
+       local dir = uci:get_first("nlbwmon", "nlbwmon", "database_directory")
+               or "/var/lib/nlbwmon"
+
+       local fp
+       http.setfilehandler(
+               function(meta, chunk, eof)
+                       if not fp and meta and meta.name == "archive" then
+                               fp = io.open(tmp, "w")
+                       end
+                       if fp and chunk then
+                               fp:write(chunk)
+                       end
+                       if fp and eof then
+                               fp:close()
+                       end
+               end)
+
+       local files = { }
+       local tar = io.popen("/bin/tar -tzf %s" % tmp, "r")
+       if tar then
+               while true do
+                       local file = tar:read("*l")
+                       if not file then
+                               break
+                       elseif file:match("^%d%d%d%d%d%d%d%d%.db%.gz$") or
+                              file:match("^%./%d%d%d%d%d%d%d%d%.db%.gz$") then
+                               files[#files+1] = file
+                       end
+               end
+               tar:close()
+       end
+
+       if #files == 0 then
+               http.status(500, "Internal Server Error")
+               tpl.render("nlbw/backup", {
+                       message = i18n.translate("Invalid or empty backup archive")
+               })
+               return
+       end
+
+
+       local output = { }
+
+       exec("/etc/init.d/nlbwmon", { "stop" })
+       exec("/bin/mkdir", { "-p", dir })
+
+       exec("/bin/tar", { "-C", dir, "-vxzf", tmp, unpack(files) },
+               function(chunk) output[#output+1] = chunk:match("%S+") end)
+
+       exec("/bin/rm", { "-f", tmp })
+       exec("/etc/init.d/nlbwmon", { "start" })
+
+       tpl.render("nlbw/backup", {
+               message = i18n.translatef(
+                       "The following database files have been restored: %s",
+                       table.concat(output, ", "))
+       })
+end
+
+function action_commit()
+       local http = require "luci.http"
+       local disp = require "luci.dispatcher"
+
+       http.redirect(disp.build_url("admin/nlbw/display"))
+       exec("/usr/sbin/nlbw", { "-c", "commit" })
+end
diff --git a/applications/luci-app-nlbwmon/luasrc/model/cbi/nlbw/config.lua b/applications/luci-app-nlbwmon/luasrc/model/cbi/nlbw/config.lua
new file mode 100644 (file)
index 0000000..71e096c
--- /dev/null
@@ -0,0 +1,215 @@
+-- Copyright 2017 Jo-Philipp Wich <jo@mein.io>
+-- Licensed to the public under the Apache License 2.0.
+
+local utl = require "luci.util"
+local sys = require "luci.sys"
+local fs  = require "nixio.fs"
+local ip  = require "luci.ip"
+local nw  = require "luci.model.network"
+
+local s, m, period, warning, date, days, interval, ifaces, subnets, limit, prealloc, compress, generations, commit, refresh, directory, protocols
+
+m = Map("nlbwmon", translate("Netlink Bandwidth Monitor - Configuration"),
+       translate("The Netlink Bandwidth Monitor (nlbwmon) is a lightweight, efficient traffic accounting program keeping track of bandwidth usage per host and protocol."))
+
+nw.init(luci.model.uci.cursor_state())
+
+s = m:section(TypedSection, "nlbwmon")
+s.anonymous = true
+s.addremove = false
+s:tab("general", translate("General Settings"))
+s:tab("advanced", translate("Advanced Settings"))
+s:tab("protocol", translate("Protocol Mapping"),
+       translate("Protocol mappings to distinguish traffic types per host, one mapping per line. The first value specifies the IP protocol, the second value the port number and the third column is the name of the mapped protocol."))
+
+period = s:taboption("general", ListValue, "_period", translate("Accounting period"),
+       translate("Choose \"Day of month\" to restart the accounting period monthly on a specific date, e.g. every 3rd. Choose \"Fixed interval\" to restart the accounting period exactly every N days, beginning at a given date."))
+
+period:value("relative", translate("Day of month"))
+period:value("absolute", translate("Fixed interval"))
+
+period.write = function(self, cfg, val)
+       if period:formvalue(cfg) == "relative" then
+               m:set(cfg, "database_interval", interval:formvalue(cfg))
+       else
+               m:set(cfg, "database_interval", "%s/%s" %{
+                       date:formvalue(cfg),
+                       days:formvalue(cfg)
+               })
+       end
+end
+
+period.cfgvalue = function(self, cfg)
+       local val = m:get(cfg, "database_interval") or ""
+       if val:match("^%d%d%d%d%-%d%d%-%d%d/%d+$") then
+               return "absolute"
+       end
+       return "relative"
+end
+
+
+warning = s:taboption("general", DummyValue, "_warning", translate("Warning"))
+warning.default = translatef("Changing the accounting interval type will invalidate existing databases!<br /><strong><a href=\"%s\">Download backup</a></strong>.", luci.dispatcher.build_url("admin/nlbw/backup"))
+warning.rawhtml = true
+
+if (m.uci:get_first("nlbwmon", "nlbwmon", "database_interval") or ""):match("^%d%d%d%d-%d%d-%d%d/%d+$") then
+       warning:depends("_period", "relative")
+else
+       warning:depends("_period", "absolute")
+end
+
+
+interval = s:taboption("general", Value, "_interval", translate("Due date"),
+       translate("Day of month to restart the accounting period. Use negative values to count towards the end of month, e.g. \"-5\" to specify the 27th of July or the 24th of Februrary."))
+
+interval.datatype = "or(range(1,31),range(-31,-1))"
+interval.placeholder = "1"
+interval:value("1", translate("1 - Restart every 1st of month"))
+interval:value("-1", translate("-1 - Restart every last day of month"))
+interval:value("-7", translate("-7 - Restart a week before end of month"))
+interval.rmempty = false
+interval:depends("_period", "relative")
+interval.write = period.write
+
+interval.cfgvalue = function(self, cfg)
+       local val = m:get(cfg, "database_interval")
+       return val and tonumber(val)
+end
+
+
+date = s:taboption("general", Value, "_date", translate("Start date"),
+       translate("Start date of the first accounting period, e.g. begin of ISP contract."))
+
+date.datatype = "dateyyyymmdd"
+date.placeholder = "2016-03-15"
+date.rmempty = false
+date:depends("_period", "absolute")
+date.write = period.write
+
+date.cfgvalue = function(self, cfg)
+       local val = m:get(cfg, "database_interval") or ""
+       return (val:match("^(%d%d%d%d%-%d%d%-%d%d)/%d+$"))
+end
+
+
+days = s:taboption("general", Value, "_days", translate("Interval"),
+       translate("Length of accounting interval in days."))
+
+days.datatype = "min(1)"
+days.placeholder = "30"
+days.rmempty = false
+days:depends("_period", "absolute")
+days.write = period.write
+
+days.cfgvalue = function(self, cfg)
+       local val = m:get(cfg, "database_interval") or ""
+       return (val:match("^%d%d%d%d%-%d%d%-%d%d/(%d+)$"))
+end
+
+
+ifaces = s:taboption("general", Value, "_ifaces", translate("Local interfaces"),
+       translate("Only conntrack streams from or to any of these networks are counted."))
+
+ifaces.template = "cbi/network_netlist"
+ifaces.widget = "checkbox"
+ifaces.nocreate = true
+
+ifaces.cfgvalue = function(self, cfg)
+       return m:get(cfg, "local_network")
+end
+
+ifaces.write = function(self, cfg)
+       local item
+       local items = {}
+       for item in utl.imatch(subnets:formvalue(cfg)) do
+               items[#items+1] = item
+       end
+       for item in utl.imatch(ifaces:formvalue(cfg)) do
+               items[#items+1] = item
+       end
+       m:set(cfg, "local_network", items)
+end
+
+
+subnets = s:taboption("general", DynamicList, "_subnets", translate("Local subnets"),
+       translate("Only conntrack streams from or to any of these subnets are counted."))
+
+subnets.datatype = "ipaddr"
+
+subnets.cfgvalue = function(self, cfg)
+       local subnet
+       local subnets = {}
+       for subnet in utl.imatch(m:get(cfg, "local_network")) do
+               subnet = ip.new(subnet)
+               subnets[#subnets+1] = subnet and subnet:string()
+       end
+       return subnets
+end
+
+subnets.write = ifaces.write
+
+
+limit = s:taboption("advanced", Value, "database_limit", translate("Maximum entries"),
+       translate("The maximum amount of entries that should be put into the database, setting the limit to 0 will allow databases to grow indefinitely."))
+
+limit.datatype = "uinteger"
+limit.placeholder = "10000"
+
+prealloc = s:taboption("advanced", Flag, "database_prealloc", translate("Preallocate database"),
+       translate("Whether to preallocate the maximum possible database size in memory. This is mainly useful for memory constrained systems which might not be able to satisfy memory allocation after longer uptime periods."))
+
+prealloc:depends({["database_limit"] = "0", ["!reverse"] = true })
+
+
+compress = s:taboption("advanced", Flag, "database_compress", translate("Compress database"),
+       translate("Whether to gzip compress archive databases. Compressing the database files makes accessing old data slightly slower but helps to reduce storage requirements."))
+
+compress.default = compress.enabled
+
+
+generations = s:taboption("advanced", Value, "database_generations", translate("Stored periods"),
+       translate("Maximum number of accounting periods to keep, use zero to keep databases forever."))
+
+generations.datatype = "uinteger"
+generations.placeholder = "10"
+
+
+commit = s:taboption("advanced", Value, "commit_interval", translate("Commit interval"),
+       translate("Interval at which the temporary in-memory database is committed to the persistent database directory."))
+
+commit.placeholder = "24h"
+commit:value("24h", translate("24h - least flash wear at the expense of data loss risk"))
+commit:value("12h", translate("12h - compromise between risk of data loss and flash wear"))
+commit:value("10m", translate("10m - frequent commits at the expense of flash wear"))
+commit:value("60s", translate("60s - commit minutely, useful for non-flash storage"))
+
+
+refresh = s:taboption("advanced", Value, "refresh_interval", translate("Refresh interval"),
+       translate("Interval at which traffic counters of still established connections are refreshed from netlink information."))
+
+refresh.placeholder = "30s"
+refresh:value("30s", translate("30s - refresh twice per minute for reasonably current stats"))
+refresh:value("5m", translate("5m - rarely refresh to avoid frequently clearing conntrack counters"))
+
+
+directory = s:taboption("advanced", Value, "database_directory", translate("Database directory"),
+       translate("Database storage directory. One file per accounting period will be placed into this directory."))
+
+directory.placeholder = "/var/lib/nlbwmon"
+
+
+protocols = s:taboption("protocol", TextValue, "_protocols")
+protocols.rows = 50
+
+protocols.cfgvalue = function(self, cfg)
+       return fs.readfile("/usr/share/nlbwmon/protocols")
+end
+
+protocols.write = function(self, cfg, value)
+       fs.writefile("/usr/share/nlbwmon/protocols", (value or ""):gsub("\r\n", "\n"))
+end
+
+protocols.remove = protocols.write
+
+
+return m
diff --git a/applications/luci-app-nlbwmon/luasrc/view/nlbw/backup.htm b/applications/luci-app-nlbwmon/luasrc/view/nlbw/backup.htm
new file mode 100644 (file)
index 0000000..ea2e0f0
--- /dev/null
@@ -0,0 +1,34 @@
+<%#
+ Copyright 2017 Jo-Philipp Wich <jo@mein.io>
+ Licensed to the public under the Apache License 2.0.
+-%>
+
+<%+header%>
+
+<script type="text/javascript" src="<%=resource%>/cbi.js"></script>
+
+<h2 name="content"><%:Netlink Bandwidth Monitor - Backup / Restore %></h2>
+
+<fieldset class="cbi-section">
+       <legend><%:Restore Database Backup%></legend>
+       <p>
+               <form method="POST" action="<%=url("admin/nlbw/restore")%>" enctype="multipart/form-data">
+                       <input type="hidden" name="token" value="<%=token%>" />
+                       <input type="file" name="archive" accept="application/gzip,.gz" />
+                       <input type="submit" value="<%:Restore%>" class="cbi-button cbi-button-apply" />
+               </form>
+
+               <% if message then %>
+                       <div class="alert-message"><%=message%></div>
+               <% end %>
+       </p>
+
+       <legend><%:Download Database Backup%></legend>
+       <p>
+               <form method="GET" action="<%=url("admin/nlbw/download")%>">
+                       <input type="submit" value="<%:Generate Backup%>" class="cbi-button cbi-button-link" />
+               </form>
+       </p>
+</fieldset>
+
+<%+footer%>
diff --git a/applications/luci-app-nlbwmon/luasrc/view/nlbw/display.htm b/applications/luci-app-nlbwmon/luasrc/view/nlbw/display.htm
new file mode 100644 (file)
index 0000000..7e85ace
--- /dev/null
@@ -0,0 +1,1052 @@
+<%#
+ Copyright 2017 Jo-Philipp Wich <jo@mein.io>
+ Licensed to the public under the Apache License 2.0.
+-%>
+
+<% css = [[
+
+       #chartjs-tooltip {
+               opacity: 0;
+               position: absolute;
+               background: rgba(0, 0, 0, .7);
+               color: white;
+               padding: 3px;
+               border-radius: 3px;
+               -webkit-transition: all .1s ease;
+               transition: all .1s ease;
+               pointer-events: none;
+               -webkit-transform: translate(-50%, 0);
+               transform: translate(-50%, 0);
+               z-index: 200;
+       }
+
+       #chartjs-tooltip.above {
+               -webkit-transform: translate(-50%, -100%);
+               transform: translate(-50%, -100%);
+       }
+
+       #chartjs-tooltip.above:before {
+               border: solid;
+               border-color: #111 transparent;
+               border-color: rgba(0, 0, 0, .8) transparent;
+               border-width: 8px 8px 0 8px;
+               bottom: 1em;
+               content: "";
+               display: block;
+               left: 50%;
+               top: 100%;
+               position: absolute;
+               z-index: 99;
+               -webkit-transform: translate(-50%, 0);
+               transform: translate(-50%, 0);
+       }
+
+       table {
+               border: 1px solid #999;
+               border-collapse: collapse;
+               margin: 0 0 2px !important;
+       }
+
+       th, td, table table td {
+               border: 1px solid #999;
+               text-align: right;
+               padding: 1px 3px !important;
+               white-space: nowrap;
+       }
+
+       tbody td {
+               border-bottom-color: #ccc;
+       }
+
+       tbody td[rowspan] {
+               border-bottom-color: #999;
+       }
+
+       tbody tr:last-child td {
+               border-bottom-color: #999;
+       }
+
+
+       .pie {
+               width: 200px;
+               display: inline-block;
+               margin: 20px;
+       }
+
+       .pie label {
+               font-weight: bold;
+               font-size: 14px;
+               display: block;
+               margin-bottom: 10px;
+               text-align: center;
+       }
+
+       .kpi {
+               display: inline-block;
+               margin: 80px 20px 20px;
+               vertical-align: top;
+       }
+
+       .kpi ul {
+               list-style: none;
+       }
+
+       .kpi li {
+               margin: 10px;
+               display: none;
+       }
+
+       .kpi big {
+               font-weight: bold;
+       }
+
+       #detail-bubble {
+               position: absolute;
+               opacity: 0;
+               visibility: hidden;
+       }
+
+       #detail-bubble.in {
+               opacity: 1;
+               visibility: visible;
+               transition: opacity 0.5s;
+       }
+
+       #detail-bubble > div {
+               border: 1px solid #ccc;
+               border-radius: 2px;
+               padding: 5px;
+               background: #fcfcfc;
+       }
+
+       #detail-bubble .head {
+               text-align: center;
+               white-space: nowrap;
+               position: relative;
+       }
+
+       #detail-bubble .head .dismiss {
+               top: 0;
+               right: 0;
+               width: 20px;
+               line-height: 20px;
+               text-align: center;
+               text-decoration: none;
+               font-weight: bold;
+               color: #000;
+               position: absolute;
+               font-size: 20px;
+       }
+
+       #detail-bubble .pie {
+               width: 100px;
+               margin: 5px;
+       }
+
+       #detail-bubble .kpi {
+               margin: 40px 5px 5px;
+               font-size: smaller;
+               text-align: left;
+       }
+
+       #detail-bubble .kpi ul {
+               margin: 0;
+       }
+
+       #bubble-arrow {
+               border: 1px solid #ccc;
+               border-width: 1px 0 0 1px;
+               background: #fcfcfc;
+               width: 15px;
+               height: 15px;
+               position: absolute;
+               left: 0;
+               top: -8px;
+               transform: rotate(45deg);
+               margin: 0 0 0 -8px;
+       }
+
+       tr.active > td {
+               border-bottom: 2px solid red;
+       }
+
+       tr.active > td.active {
+               border: 2px solid red;
+               border-bottom: none;
+       }
+
+       td.detail {
+               border: 2px solid red;
+               border-top: none;
+               opacity: 0;
+               transition: opacity 0.5s;
+       }
+
+       td.detail.in {
+               opacity: 1;
+       }
+
+       th.hostname,
+       td.hostname {
+               text-align: left;
+       }
+
+]] -%>
+
+<%+header%>
+
+<script type="text/javascript" src="<%=resource%>/cbi.js"></script>
+<script type="text/javascript" src="<%=resource%>/nlbw.chart.min.js"></script>
+<script type="text/javascript">//<![CDATA[
+
+var chartRegistry = {},
+       trafficPeriods = [],
+       trafficData = { columns: [], data: [] },
+       hostNames = {},
+       hostInfo = <%=luci.util.serialize_json(luci.sys.net.host_hints())%>,
+       ouiData = [];
+
+
+function off(elem)
+{
+       var val = [0, 0];
+       do {
+               if (!isNaN(elem.offsetLeft) && !isNaN(elem.offsetTop)) {
+                       val[0] += elem.offsetLeft;
+                       val[1] += elem.offsetTop;
+               }
+       }
+       while ((elem = elem.offsetParent) != null);
+       return val;
+}
+
+Chart.defaults.global.customTooltips = function(tooltip) {
+       var tooltipEl = document.getElementById('chartjs-tooltip');
+
+       if (!tooltipEl) {
+               tooltipEl = document.createElement('div');
+               tooltipEl.setAttribute('id', 'chartjs-tooltip');
+               document.body.appendChild(tooltipEl);
+       }
+
+       if (!tooltip) {
+               if (tooltipEl.row)
+                       tooltipEl.row.style.backgroundColor = '';
+
+               tooltipEl.style.opacity = 0;
+               return;
+       }
+
+       var pos = off(tooltip.chart.canvas);
+
+       tooltipEl.className = tooltip.yAlign;
+       tooltipEl.innerHTML = tooltip.text[0];
+
+       tooltipEl.style.opacity = 1;
+       tooltipEl.style.left = pos[0] + tooltip.x + 'px';
+       tooltipEl.style.top = pos[1] + tooltip.y - tooltip.caretHeight - tooltip.caretPadding + 'px';
+
+       var row = tooltip.text[1],
+           hue = tooltip.text[2];
+
+       if (row && !isNaN(hue)) {
+               row.style.backgroundColor = 'hsl(%u, 100%%, 80%%)'.format(hue);
+               tooltipEl.row = row;
+       }
+};
+
+Chart.defaults.global.tooltipFontSize = 10;
+Chart.defaults.global.tooltipTemplate = function(tip) {
+       tip.label[0] = tip.label[0].format(tip.value);
+       return tip.label;
+};
+
+function kpi(id, val1, val2, val3)
+{
+       var e = document.getElementById(id);
+
+       if (val1 && val2 && val3)
+               e.innerHTML = '<%:%s, %s and %s%>'.format(val1, val2, val3);
+       else if (val1 && val2)
+               e.innerHTML = '<%:%s and %s%>'.format(val1, val2);
+       else if (val1)
+               e.innerHTML = val1;
+
+       e.parentNode.style.display = val1 ? 'list-item' : '';
+}
+
+function pie(id, data)
+{
+       data.sort(function(a, b) { return b.value - a.value });
+
+       if (data.length === 0 || (data.length === 1 && data[0].value === 0))
+               data[0] = {
+                       value: 1,
+                       color: '#cccccc',
+                       label: [ '<%:no traffic%>' ]
+               };
+
+       for (var i = 0; i < data.length; i++) {
+               if (!data[i].color) {
+                       var hue = 120 / (data.length-1) * i;
+                       data[i].color = 'hsl(%u, 80%%, 50%%)'.format(hue);
+                       data[i].label.push(hue);
+               }
+       }
+
+       var ctx = document.getElementById(id).getContext('2d');
+
+       if (chartRegistry.hasOwnProperty(id))
+               chartRegistry[id].destroy();
+
+       chartRegistry[id] = new Chart(ctx).Doughnut(data, {
+               segmentStrokeWidth: 1,
+               percentageInnerCutout: 30
+       });
+
+       return chartRegistry[id];
+}
+
+function query(filter, group, order)
+{
+       var keys = [], columns = {}, records = {}, result = [];
+
+       if (typeof(group) !== 'function' && typeof(group) !== 'object')
+               group = ['mac'];
+
+       for (var i = 0; i < trafficData.columns.length; i++)
+               columns[trafficData.columns[i]] = i;
+
+       for (var i = 0; i < trafficData.data.length; i++) {
+               var record = trafficData.data[i];
+
+               if (typeof(filter) === 'function' && filter(columns, record) !== true)
+                       continue;
+
+               var key;
+
+               if (typeof(group) === 'function') {
+                       key = group(columns, record);
+               }
+               else {
+                       key = [];
+
+                       for (var j = 0; j < group.length; j++)
+                               if (columns.hasOwnProperty(group[j]))
+                                       key.push(record[columns[group[j]]]);
+
+                       key = key.join(',');
+               }
+
+               if (!records.hasOwnProperty(key)) {
+                       var rec = {};
+
+                       for (var col in columns)
+                               rec[col] = record[columns[col]];
+
+                       records[key] = rec;
+                       result.push(rec);
+               }
+               else {
+                       records[key].conns    += record[columns.conns];
+                       records[key].rx_bytes += record[columns.rx_bytes];
+                       records[key].rx_pkts  += record[columns.rx_pkts];
+                       records[key].tx_bytes += record[columns.tx_bytes];
+                       records[key].tx_pkts  += record[columns.tx_pkts];
+               }
+       }
+
+       if (typeof(order) === 'function')
+               result.sort(order);
+
+       return result;
+}
+
+function oui(mac) {
+       var m, l = 0, r = ouiData.length / 3 - 1;
+       var mac1 = parseInt(mac.replace(/[^a-fA-F0-9]/g, ''), 16);
+
+       while (l <= r) {
+               m = l + Math.floor((r - l) / 2);
+
+               var mask = (0xffffffffffff -
+                                       (Math.pow(2, 48 - ouiData[m * 3 + 1]) - 1));
+
+               var mac1_hi = ((mac1 / 0x10000) & (mask / 0x10000)) >>> 0;
+               var mac1_lo = ((mac1 &  0xffff) & (mask &  0xffff)) >>> 0;
+
+               var mac2 = parseInt(ouiData[m * 3], 16);
+               var mac2_hi = (mac2 / 0x10000) >>> 0;
+               var mac2_lo = (mac2 &  0xffff) >>> 0;
+
+               if (mac1_hi === mac2_hi && mac1_lo === mac2_lo)
+                       return ouiData[m * 3 + 2];
+
+               if (mac2_hi > mac1_hi ||
+                       (mac2_hi === mac1_hi && mac2_lo > mac1_lo))
+                       r = m - 1;
+               else
+                       l = m + 1;
+       }
+
+       return null;
+}
+
+
+function fetchData(period)
+{
+       XHR.get('<%=url("admin/nlbw/data")%>', { period: period, group_by: 'family,mac,ip,layer7', order_by: '-rx_bytes,-tx_bytes' }, function(xhr, res) {
+               if (res !== null && typeof(res) === 'object' && typeof(res.columns) === 'object' && typeof(res.data) === 'object')
+                       trafficData = res;
+
+               var addrs = query(null, ['ip'], null);
+               var ipAddrs = [];
+
+               for (var i = 0; i < addrs.length; i++)
+                       if (ipAddrs.indexOf(addrs[i].ip) < 0)
+                               ipAddrs.push(addrs[i].ip);
+
+               renderHostData();
+               renderLayer7Data();
+               renderIPv6Data();
+
+               XHR.get('<%=url("admin/nlbw/ptr")%>/' + ipAddrs.join('/'), null, function(xhr, res) {
+                       if (res !== null && typeof(res) === 'object')
+                               hostNames = res;
+               });
+       });
+}
+
+function switchTab(tab)
+{
+       bubbleDismiss();
+
+       return cbi_t_switch('nlbw', tab);
+}
+
+function renderPeriods()
+{
+       var sel = document.getElementById('nlbw.period');
+
+       for (var e, i = trafficPeriods.length - 1; e = trafficPeriods[i]; i--) {
+               var d1 = new Date(e);
+               var d2, pd;
+
+               if (i) {
+                       d2 = new Date(trafficPeriods[i - 1]);
+                       d2.setDate(d2.getDate() - 1);
+                       pd = '%04d-%02d-%02d'.format(d1.getFullYear(), d1.getMonth() + 1, d1.getDate());
+               }
+               else {
+                       d2 = new Date();
+                       pd = '';
+               }
+
+               var opt = document.createElement('option');
+                   opt.setAttribute('data-duration', (d2.getTime() - d1.getTime()) / 1000);
+                   opt.value = pd;
+                   opt.text = '%04d-%02d-%02d - %04d-%02d-%02d'.format(
+                               d1.getFullYear(), d1.getMonth() + 1, d1.getDate(),
+                               d2.getFullYear(), d2.getMonth() + 1, d2.getDate());
+
+               sel.appendChild(opt);
+       }
+
+       sel.selectedIndex = sel.childNodes.length - 1;
+       sel.style.display = '';
+
+       sel.onchange = function() {
+               bubbleDismiss();
+               fetchData(sel.options[sel.selectedIndex].value);
+       }
+}
+
+function renderHostDetail()
+{
+       var key = this.getAttribute('href').substr(1),
+           col = this.getAttribute('data-col'),
+           label = this.getAttribute('data-label'),
+           bubble = document.getElementById('detail-bubble'),
+           arrow = document.getElementById('bubble-arrow'),
+           table = document.getElementById('bubble-table');
+
+       bubbleDismiss();
+
+       var detailData = query(
+               function(c, r) {
+                       return ((r[c.mac] === key || r[c.ip] === key) &&
+                               (r[c.rx_bytes] > 0 || r[c.tx_bytes] > 0));
+               },
+               [col],
+               function(r1, r2) {
+                       return ((r2.rx_bytes + r2.tx_bytes) - (r1.rx_bytes + r1.tx_bytes));
+               }
+       );
+
+       var rxData = [], txData = [];
+
+       table.innerHTML = '<tr>' +
+               '<th>%s</th>'.format(label || col) +
+               '<th><%:Conn.%></th>' +
+               '<th colspan="2"><%:Down. (Bytes / Pkts.)%></th>' +
+               '<th colspan="2"><%:Up. (Bytes / Pkts.)%></th>' +
+       '</tr>';
+
+       for (var i = 0; i < detailData.length; i++) {
+               var rec = detailData[i],
+                   row = table.insertRow(-1);
+
+               row.insertCell(-1).innerHTML = rec[col] || '<%:other%>';
+               row.insertCell(-1).innerHTML = "%1000.2m".format(rec.conns);
+               row.insertCell(-1).innerHTML = "%1024.2mB".format(rec.rx_bytes);
+               row.insertCell(-1).innerHTML = "%1000.2mP".format(rec.rx_pkts);
+               row.insertCell(-1).innerHTML = "%1024.2mB".format(rec.tx_bytes);
+               row.insertCell(-1).innerHTML = "%1000.2mP".format(rec.tx_pkts);
+
+               rxData.push({
+                       label: ['%s: %%1024.2mB'.format(rec[col] || '<%:other%>'), row],
+                       value: rec.rx_bytes
+               });
+
+               txData.push({
+                       label: ['%s: %%1024.2mB'.format(rec[col] || '<%:other%>'), row],
+                       value: rec.tx_bytes
+               });
+       }
+
+       pie('bubble-pie1', rxData);
+       pie('bubble-pie2', txData);
+
+       var mac = key.toUpperCase();
+       var name = hostInfo.hasOwnProperty(mac) ? hostInfo[mac].name : null;
+
+       if (!name)
+               for (var i = 0; i < detailData.length; i++)
+                       if ((name = hostNames[detailData[i].ip]) !== undefined)
+                               break;
+
+       if (mac !== '00:00:00:00:00:00') {
+               kpi('bubble-hostname', name);
+               kpi('bubble-vendor', oui(mac));
+       }
+       else {
+               kpi('bubble-hostname');
+               kpi('bubble-vendor');
+       }
+
+       var tr = this.parentNode.parentNode,
+           xy = off(tr),
+           xy2 = off(this);
+
+       bubble.style.width = tr.offsetWidth + 'px';
+       bubble.style.left = xy[0] + 'px';
+       bubble.style.top = (xy[1] + tr.offsetHeight) + 'px';
+       arrow.style.left = Math.floor(xy2[0] + this.offsetWidth / 2 - xy[0]) + 'px';
+
+       bubble.className = 'in';
+
+       return false;
+}
+
+function formatHostname(dns)
+{
+       if (dns === undefined || dns === null || dns === '')
+               return '-';
+
+       dns = dns.split('.')[0];
+
+       if (dns.length > 12)
+               return '<span title="%q">%h…</span>'.format(dns, dns.substr(0, 12));
+
+       return '%h'.format(dns);
+}
+
+function renderHostData()
+{
+       var trafData = [], connData = [];
+       var rx_total = 0, tx_total = 0, conn_total = 0;
+       var table = document.getElementById('host-data');
+
+       var hostData = query(
+               function(c, r) {
+                       return (r[c.rx_bytes] > 0 || r[c.tx_bytes] > 0);
+               },
+               ['mac'],
+               //function(c, r) {
+               //      return (r[c.mac] !== '00:00:00:00:00:00') ? r[c.mac] : r[c.ip];
+               //},
+               function(r1, r2) {
+                       return ((r2.rx_bytes + r2.tx_bytes) - (r1.rx_bytes + r1.tx_bytes));
+               }
+       );
+
+       while (table.rows.length > 1)
+               table.deleteRow(1);
+
+       for (var i = 0; i < hostData.length; i++) {
+               var row = table.insertRow(-1),
+                   cell = row.insertCell(-1),
+                   rec = hostData[i],
+                   mac = rec.mac.toUpperCase(),
+                   key = (mac !== '00:00:00:00:00:00') ? mac : rec.ip,
+                   dns = hostInfo[mac] ? hostInfo[mac].name : null;
+
+               var link1 = document.createElement('a');
+                   link1.onclick = renderHostDetail;
+                   link1.href = '#' + rec.mac;
+                   link1.setAttribute('data-col', 'ip');
+                   link1.setAttribute('data-label', '<%:Source IP%>');
+                   link1.innerHTML = (mac !== '00:00:00:00:00:00') ? mac : '<%:other%>';
+
+               var link2 = document.createElement('a');
+                   link2.onclick = renderHostDetail;
+                   link2.href = '#' + rec.mac;
+                   link2.setAttribute('data-col', 'layer7');
+                   link2.setAttribute('data-label', '<%:Protocol%>');
+                   link2.innerHTML = "%1000.2m".format(rec.conns);
+
+               cell.innerHTML = formatHostname(dns);
+               cell.className = 'hostname';
+
+               row.insertCell(-1).appendChild(link1);
+               row.insertCell(-1).appendChild(link2);
+               row.insertCell(-1).innerHTML = "%1024.2mB".format(rec.rx_bytes);
+               row.insertCell(-1).innerHTML = "%1000.2mP".format(rec.rx_pkts);
+               row.insertCell(-1).innerHTML = "%1024.2mB".format(rec.tx_bytes);
+               row.insertCell(-1).innerHTML = "%1000.2mP".format(rec.tx_pkts);
+
+               trafData.push({
+                       value: rec.rx_bytes + rec.tx_bytes,
+                       label: ["%s: %%.2mB".format(key), row]
+               });
+
+               connData.push({
+                       value: rec.conns,
+                       label: ["%s: %%.2m".format(key), row]
+               });
+
+               rx_total += rec.rx_bytes;
+               tx_total += rec.tx_bytes;
+               conn_total += rec.conns;
+       }
+
+       if (table.rows.length === 1) {
+               var cell = table.insertRow(-1).insertCell(-1);
+
+               cell.setAttribute('colspan', 6);
+               cell.innerHTML = '<em><%:No data recorded yet.%> <a href="<%=url("admin/nlbw/commit")%>"><%:Force reload…%></a></em>';
+       }
+
+       pie('traf-pie', trafData);
+       pie('conn-pie', connData);
+
+       kpi('rx-total', '%1024.2mB'.format(rx_total));
+       kpi('tx-total', '%1024.2mB'.format(tx_total));
+       kpi('conn-total', '%1000m'.format(conn_total));
+       kpi('host-total', '%u'.format(hostData.length));
+}
+
+function renderLayer7Data()
+{
+       var rxData = [], txData = [];
+       var topConn = [[0],[0],[0]], topRx = [[0],[0],[0]], topTx = [[0],[0],[0]];
+       var table = document.getElementById('layer7-data');
+
+       var layer7Data = query(
+               null, ['layer7'],
+               function(r1, r2) {
+                       return ((r2.rx_bytes + r2.tx_bytes) - (r1.rx_bytes + r1.tx_bytes));
+               }
+       );
+
+       while (table.rows.length > 1)
+               table.deleteRow(1);
+
+       for (var i = 0, c = 0; i < layer7Data.length; i++) {
+               var rec = layer7Data[i],
+                   row = table.insertRow(-1);
+
+               rxData.push({
+                       value: rec.rx_bytes,
+                       label: ["%s: %%.2mB".format(rec.layer7 || '<%:other%>'), row]
+               });
+
+               txData.push({
+                       value: rec.tx_bytes,
+                       label: ["%s: %%.2mB".format(rec.layer7 || '<%:other%>'), row]
+               });
+
+               row.insertCell(-1).innerHTML = rec.layer7 || '<%:other%>';
+               row.insertCell(-1).innerHTML = "%1000m".format(rec.conns);
+               row.insertCell(-1).innerHTML = "%1024.2mB".format(rec.rx_bytes);
+               row.insertCell(-1).innerHTML = "%1000.2mP".format(rec.rx_pkts);
+               row.insertCell(-1).innerHTML = "%1024.2mB".format(rec.tx_bytes);
+               row.insertCell(-1).innerHTML = "%1000.2mP".format(rec.tx_pkts);
+
+               if (rec.layer7) {
+                       topRx.push([rec.rx_bytes, rec.layer7]);
+                       topTx.push([rec.tx_bytes, rec.layer7]);
+                       topConn.push([rec.conns, rec.layer7]);
+               }
+       }
+
+       if (table.rows.length === 1) {
+               var cell = table.insertRow(-1).insertCell(-1);
+
+               cell.setAttribute('colspan', 6);
+               cell.innerHTML = '<em><%:No data recorded yet.%> <a href="<%=url("admin/nlbw/commit")%>"><%:Force reload…%></a></em>';
+       }
+
+       pie('layer7-rx-pie', rxData);
+       pie('layer7-tx-pie', txData);
+
+       topRx.sort(function(a, b) { return b[0] - a[0] });
+       topTx.sort(function(a, b) { return b[0] - a[0] });
+       topConn.sort(function(a, b) { return b[0] - a[0] });
+
+       kpi('layer7-total', layer7Data.length);
+       kpi('layer7-most-rx', topRx[0][1], topRx[1][1], topRx[2][1]);
+       kpi('layer7-most-tx', topTx[0][1], topTx[1][1], topTx[2][1]);
+       kpi('layer7-most-conn', topConn[0][1], topConn[1][1], topConn[2][1]);
+}
+
+function renderIPv6Data()
+{
+       var table     = document.getElementById('ipv6-data'),
+           col       = { },
+           rx4_total = 0,
+           tx4_total = 0,
+           rx6_total = 0,
+           tx6_total = 0,
+           v4_total  = 0,
+           v6_total  = 0,
+           ds_total  = 0,
+           families  = { },
+           records   = { };
+
+       ipv6Data = query(
+               null, ['family', 'mac'],
+               function(r1, r2) {
+                       return ((r2.rx_bytes + r2.tx_bytes) - (r1.rx_bytes + r1.tx_bytes));
+               }
+       );
+
+       for (var i = 0, c = 0; i < ipv6Data.length; i++) {
+               var rec = ipv6Data[i],
+                   mac = rec.mac.toUpperCase(),
+                   ip  = rec.ip,
+                   fam = families[mac] || 0,
+                   recs = records[mac] || {};
+
+               if (rec.family == 4) {
+                       rx4_total += rec.rx_bytes;
+                       tx4_total += rec.tx_bytes;
+                       fam |= 1;
+               }
+               else {
+                       rx6_total += rec.rx_bytes;
+                       tx6_total += rec.tx_bytes;
+                       fam |= 2;
+               }
+
+               recs[rec.family] = rec;
+               records[mac] = recs;
+
+               families[mac] = fam;
+       }
+
+       for (var mac in families) {
+               switch (families[mac])
+               {
+               case 3:
+                       ds_total++;
+                       break;
+
+               case 2:
+                       v6_total++;
+                       break;
+
+               case 1:
+                       v4_total++;
+                       break;
+               }
+       }
+
+       while (table.rows.length > 1)
+               table.deleteRow(1);
+
+       for (var mac in records) {
+               if (mac === '00:00:00:00:00:00')
+                       continue;
+
+               var row = table.insertRow(-1),
+                   cell1 = row.insertCell(-1),
+                   cell2 = row.insertCell(-1),
+                   dns = hostInfo[mac] ? hostInfo[mac].name : null,
+                   rec4 = records[mac][4],
+                   rec6 = records[mac][6];
+
+               cell1.setAttribute('rowspan', 2);
+               cell1.innerHTML = formatHostname(dns);
+               cell1.className = 'hostname';
+
+               cell2.setAttribute('rowspan', 2);
+               cell2.innerHTML = mac;
+
+               row.insertCell(-1).innerHTML = 'IPv4';
+               row.insertCell(-1).innerHTML = rec4 ? "%1024.2mB".format(rec4.rx_bytes) : '-';
+               row.insertCell(-1).innerHTML = rec4 ? "%1000.2mP".format(rec4.rx_pkts)  : '-';
+               row.insertCell(-1).innerHTML = rec4 ? "%1024.2mB".format(rec4.tx_bytes) : '-';
+               row.insertCell(-1).innerHTML = rec4 ? "%1000.2mP".format(rec4.tx_pkts)  : '-';
+
+               row = table.insertRow(-1);
+
+               row.insertCell(-1).innerHTML = 'IPv6';
+               row.insertCell(-1).innerHTML = rec6 ? "%1024.2mB".format(rec6.rx_bytes) : '-';
+               row.insertCell(-1).innerHTML = rec6 ? "%1000.2mP".format(rec6.rx_pkts)  : '-';
+               row.insertCell(-1).innerHTML = rec6 ? "%1024.2mB".format(rec6.tx_bytes) : '-';
+               row.insertCell(-1).innerHTML = rec6 ? "%1000.2mP".format(rec6.tx_pkts)  : '-';
+       }
+
+       if (table.rows.length === 1) {
+               var cell = table.insertRow(-1).insertCell(-1);
+
+               cell.setAttribute('colspan', 7);
+               cell.innerHTML = '<em><%:No data recorded yet.%> <a href="<%=url("admin/nlbw/commit")%>"><%:Force reload…%></a></em>';
+       }
+
+       var shareData = [], hostsData = [];
+
+       if (rx4_total > 0 || tx4_total > 0)
+               shareData.push({
+                       value: rx4_total + tx4_total,
+                       label: ["IPv4: %.2mB"],
+                       color: 'hsl(140, 100%, 50%)'
+               });
+
+       if (rx6_total > 0 || tx6_total > 0)
+               shareData.push({
+                       value: rx6_total + tx6_total,
+                       label: ["IPv6: %.2mB"],
+                       color: 'hsl(180, 100%, 50%)'
+               });
+
+       if (v4_total > 0)
+               hostsData.push({
+                       value: v4_total,
+                       label: ["<%:%d IPv4-only hosts%>"],
+                       color: 'hsl(140, 100%, 50%)'
+               });
+
+       if (v6_total > 0)
+               hostsData.push({
+                       value: v6_total,
+                       label: ["<%:%d IPv6-only hosts%>"],
+                       color: 'hsl(180, 100%, 50%)'
+               });
+
+       if (ds_total > 0)
+               hostsData.push({
+                       value: ds_total,
+                       label: ["<%:%d dual-stack hosts%>"],
+                       color: 'hsl(50, 100%, 50%)'
+               });
+
+       pie('ipv6-share-pie', shareData);
+       pie('ipv6-hosts-pie', hostsData);
+
+       kpi('ipv6-hosts', '%.2f%%'.format(100 / (ds_total + v4_total + v6_total) * (ds_total + v6_total)));
+       kpi('ipv6-share', '%.2f%%'.format(100 / (rx4_total + rx6_total + tx4_total + tx6_total) * (rx6_total + tx6_total)));
+       kpi('ipv6-rx', '%1024.2mB'.format(rx6_total));
+       kpi('ipv6-tx', '%1024.2mB'.format(tx6_total));
+}
+
+function bubbleDismiss()
+{
+       var bubble = document.getElementById('detail-bubble');
+
+       bubble.className = '';
+       document.body.appendChild(bubble);
+
+       return false;
+}
+
+
+//]]></script>
+
+<h2 name="content"><%:Netlink Bandwidth Monitor%></h2>
+
+<div id="detail-bubble">
+       <span id="bubble-arrow"></span>
+       <div>
+               <div class="head">
+                       <a class="dismiss" href="#" onclick="this.blur(); return bubbleDismiss()">×</a>
+                       <div class="pie">
+                               <label>Download</label>
+                               <canvas id="bubble-pie1" width="100" height="100"></canvas>
+                       </div>
+                       <div class="pie">
+                               <label>Upload</label>
+                               <canvas id="bubble-pie2" width="100" height="100"></canvas>
+                       </div>
+                       <div class="kpi">
+                               <ul>
+                                       <li><%_Hostname: <big id="bubble-hostname">example.org</big>%></li>
+                                       <li><%_Vendor: <big id="bubble-vendor">Example Corp.</big>%></li>
+                               </ul>
+                       </div>
+               </div>
+               <table id="bubble-table"></table>
+       </div>
+</div>
+
+<hr>
+
+<p>
+       <%:Select accounting period:%>
+       <select id="nlbw.period" style="display:none"></select>
+</p>
+
+<hr>
+
+<ul class="cbi-tabmenu">
+       <li id="tab.nlbw.traffic" class="cbi-tab"><a href="#" onclick="return switchTab('traffic')"><%:Traffic Distribution%></a></li>
+       <li id="tab.nlbw.layer7" class="cbi-tab-disabled"><a href="#" onclick="return switchTab('layer7')"><%:Application Protocols%></a></li>
+       <li id="tab.nlbw.ipv6" class="cbi-tab-disabled"><a href="#" onclick="return switchTab('ipv6')"><%:IPv6%></a></li>
+       <li id="tab.nlbw.export" class="cbi-tab-disabled"><a href="#" onclick="return switchTab('export')"><%:Export%></a></li>
+</ul>
+
+<div class="cbi-section" id="container.nlbw.traffic">
+       <div>
+               <div class="pie">
+                       <label><%:Traffic / Host%></label>
+                       <canvas id="traf-pie" width="200" height="200"></canvas>
+               </div>
+
+               <div class="pie">
+                       <label><%:Connections / Host%></label>
+                       <canvas id="conn-pie" width="200" height="200"></canvas>
+               </div>
+
+               <div class="kpi">
+                       <ul>
+                               <li><%_<big id="host-total">0</big> hosts%></li>
+                               <li><%_<big id="rx-total">0</big> download%></li>
+                               <li><%_<big id="tx-total">0</big> upload%></li>
+                               <li><%_<big id="conn-total">0</big> connections%></li>
+                       </ul>
+               </div>
+       </div>
+       <table id="host-data">
+               <tr>
+                       <th width="10%" class="hostname"><%:Host%></th>
+                       <th width="5%"><%:MAC%></th>
+                       <th width="5%"><%:Connections%></th>
+                       <th width="30%" colspan="2"><%:Download (Bytes / Packets)%></th>
+                       <th width="30%" colspan="2"><%:Upload (Bytes / Packets)%></th>
+               </tr>
+       </table>
+</div>
+
+<div class="cbi-section" id="container.nlbw.layer7" style="display:none">
+       <div>
+               <div class="pie">
+                       <label><%:Download / Application%></label>
+                       <canvas id="layer7-rx-pie" width="200" height="200"></canvas>
+               </div>
+
+               <div class="pie">
+                       <label><%:Upload / Application%></label>
+                       <canvas id="layer7-tx-pie" width="200" height="200"></canvas>
+               </div>
+
+               <div class="kpi">
+                       <ul>
+                               <li><%_<big id="layer7-total">0</big> different application protocols%></li>
+                               <li><%_<big id="layer7-most-rx">0</big> cause the most download%></li>
+                               <li><%_<big id="layer7-most-tx">0</big> cause the most upload%></li>
+                               <li><%_<big id="layer7-most-conn">0</big> cause the most connections%></li>
+                       </ul>
+               </div>
+       </div>
+       <table id="layer7-data">
+               <tr>
+                       <th width="20%"><%:Application%></th>
+                       <th width="10%"><%:Connections%></th>
+                       <th width="30%" colspan="2"><%:Download (Bytes / Packets)%></th>
+                       <th width="30%" colspan="2"><%:Upload (Bytes / Packets)%></th>
+               </tr>
+       </table>
+</div>
+
+<div class="cbi-section" id="container.nlbw.ipv6" style="display:none">
+       <div>
+               <div class="pie">
+                       <label><%:IPv4 vs. IPv6%></label>
+                       <canvas id="ipv6-share-pie" width="200" height="200"></canvas>
+               </div>
+
+               <div class="pie">
+                       <label><%:Dualstack enabled hosts%></label>
+                       <canvas id="ipv6-hosts-pie" width="200" height="200"></canvas>
+               </div>
+
+               <div class="kpi">
+                       <ul>
+                               <li><%_<big id="ipv6-hosts">0%</big> IPv6 support rate among hosts%></li>
+                               <li><%_<big id="ipv6-share">0%</big> of the total traffic is IPv6%></li>
+                               <li><%_<big id="ipv6-rx">0B</big> total IPv6 download%></li>
+                               <li><%_<big id="ipv6-tx">0B</big> total IPv6 upload%></li>
+                       </ul>
+               </div>
+       </div>
+       <table id="ipv6-data">
+               <tr>
+                       <th width="10%" class="hostname"><%:Host%></th>
+                       <th width="5%"><%:MAC%></th>
+                       <th width="5%"><%:Family%></th>
+                       <th width="40%" colspan="2"><%:Download (Bytes / Packets)%></th>
+                       <th width="40%" colspan="2"><%:Upload (Bytes / Packets)%></th>
+               </tr>
+       </table>
+</div>
+
+<div class="cbi-section" id="container.nlbw.export" style="display:none">
+       <ul>
+               <li><a href="<%=url('admin/nlbw/data')%>?type=csv&#38;group_by=mac&#38;order_by=-rx,-tx"><%:CSV, grouped by MAC%></a></li>
+               <li><a href="<%=url('admin/nlbw/data')%>?type=csv&#38;group_by=ip&#38;order_by=-rx,-tx"><%:CSV, grouped by IP%></a></li>
+               <li><a href="<%=url('admin/nlbw/data')%>?type=csv&#38;group_by=layer7&#38;order_by=-rx,-tx"><%:CSV, grouped by protocol%></a></li>
+               <li><a href="<%=url('admin/nlbw/data')%>?type=json"><%:JSON dump%></a></li>
+       </ul>
+</div>
+
+<script type="text/javascript">//<![CDATA[
+       cbi_t_add('nlbw', 'traffic');
+       cbi_t_add('nlbw', 'layer7');
+       cbi_t_add('nlbw', 'ipv6');
+       cbi_t_add('nlbw', 'export');
+
+       XHR.get('<%=url("admin/nlbw/list")%>', null, function(xhr, res) {
+
+               if (res !== null && typeof(res) === 'object' && res.length > 0) {
+                       trafficPeriods = res;
+                       renderPeriods();
+               }
+
+               xhr.open('GET', 'https://raw.githubusercontent.com/jow-/oui-database/master/oui.json', true);
+               xhr.onreadystatechange = function() {
+                       if (xhr.readyState === 4) {
+                               try { res = JSON.parse(xhr.responseText); }
+                               catch(e) { res = null; }
+
+                               if (res !== null && typeof(res) === 'object' && (res.length % 3) === 0)
+                                       ouiData = res;
+
+                               fetchData('');
+                       }
+               };
+               xhr.send(null);
+       });
+//]]></script>
+
+<%+footer%>
diff --git a/applications/luci-app-nlbwmon/po/ja/nlbwmon.po b/applications/luci-app-nlbwmon/po/ja/nlbwmon.po
new file mode 100644 (file)
index 0000000..b5931e0
--- /dev/null
@@ -0,0 +1,387 @@
+msgid ""
+msgstr ""
+"Content-Type: text/plain; charset=UTF-8\n"
+"Project-Id-Version: \n"
+"POT-Creation-Date: \n"
+"PO-Revision-Date: \n"
+"Last-Translator: INAGAKI Hiroshi <musashino.open@gmail.com>\n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: ja\n"
+"X-Generator: Poedit 2.0.3\n"
+
+msgid "%d IPv4-only hosts"
+msgstr "%d IPv4 限定ホスト"
+
+msgid "%d IPv6-only hosts"
+msgstr "%d IPv6 限定ホスト"
+
+msgid "%d dual-stack hosts"
+msgstr "%d デュアルスタック ホスト"
+
+msgid "%s and %s"
+msgstr "%s, %s"
+
+msgid "%s, %s and %s"
+msgstr "%s, %s, %s"
+
+msgid "-1 - Restart every last day of month"
+msgstr "-1 - 月の最終日"
+
+msgid "-7 - Restart a week before end of month"
+msgstr "-7 - 月の最終日の一週間前"
+
+msgid "1 - Restart every 1st of month"
+msgstr "1 - 毎月1日"
+
+msgid "10m - frequent commits at the expense of flash wear"
+msgstr "10m - フラッシュ媒体への負荷が高い頻繁なコミット(10分)"
+
+msgid "12h - compromise between risk of data loss and flash wear"
+msgstr "12h - データ消失リスクとフラッシュ媒体への負荷の妥協点(12時間)"
+
+msgid "24h - least flash wear at the expense of data loss risk"
+msgstr "24h - データ消失リスクは高いがフラッシュ媒体への負荷は最小(24時間)"
+
+msgid "30s - refresh twice per minute for reasonably current stats"
+msgstr "30s - 現在の状態の把握に適切な1分間に2回のリフレッシュ(30秒)"
+
+msgid "5m - rarely refresh to avoid frequently clearing conntrack counters"
+msgstr ""
+
+msgid "60s - commit minutely, useful for non-flash storage"
+msgstr "60秒 - 1分毎のコミット、非フラッシュ ストレージに有用"
+
+msgid "<big id=\"conn-total\">0</big> connections"
+msgstr "<big id=\"conn-total\">0</big> 接続数"
+
+msgid "<big id=\"host-total\">0</big> hosts"
+msgstr "<big id=\"host-total\">0</big> ホスト数"
+
+msgid "<big id=\"ipv6-hosts\">0%</big> IPv6 support rate among hosts"
+msgstr "<big id=\"ipv6-hosts\">0%</big> 全ホスト中の IPv6 サポート比率"
+
+msgid "<big id=\"ipv6-rx\">0B</big> total IPv6 download"
+msgstr "<big id=\"ipv6-rx\">0B</big> IPv6 総ダウンロード"
+
+msgid "<big id=\"ipv6-share\">0%</big> of the total traffic is IPv6"
+msgstr "<big id=\"ipv6-share\">0%</big> 全トラフィック中の IPv6 の割合"
+
+msgid "<big id=\"ipv6-tx\">0B</big> total IPv6 upload"
+msgstr "<big id=\"ipv6-tx\">0B</big> IPv6 総アップロード"
+
+msgid "<big id=\"layer7-most-conn\">0</big> cause the most connections"
+msgstr "<big id=\"layer7-most-conn\">0</big> 接続数上位"
+
+msgid "<big id=\"layer7-most-rx\">0</big> cause the most download"
+msgstr "<big id=\"layer7-most-rx\">0</big> ダウンロード上位"
+
+msgid "<big id=\"layer7-most-tx\">0</big> cause the most upload"
+msgstr "<big id=\"layer7-most-tx\">0</big> アップロード上位"
+
+msgid "<big id=\"layer7-total\">0</big> different application protocols"
+msgstr "<big id=\"layer7-total\">0</big> アプリケーション プロトコル数"
+
+msgid "<big id=\"rx-total\">0</big> download"
+msgstr "<big id=\"rx-total\">0</big> ダウンロード"
+
+msgid "<big id=\"tx-total\">0</big> upload"
+msgstr "<big id=\"tx-total\">0</big> アップロード"
+
+msgid "Accounting period"
+msgstr "収集期間"
+
+msgid "Advanced Settings"
+msgstr "拡張設定"
+
+msgid "Application"
+msgstr "アプリケーション"
+
+msgid "Application Protocols"
+msgstr "アプリケーション プロトコル"
+
+msgid "Backup"
+msgstr "バックアップ"
+
+msgid "Bandwidth Monitor"
+msgstr "帯域幅モニター"
+
+msgid "CSV, grouped by IP"
+msgstr "CSV(IP によるグループ化)"
+
+msgid "CSV, grouped by MAC"
+msgstr "CSV(MAC によるグループ化)"
+
+msgid "CSV, grouped by protocol"
+msgstr "CSV(プロトコルによるグループ化)"
+
+msgid ""
+"Changing the accounting interval type will invalidate existing databases!"
+"<br /><strong><a href=\"%s\">Download backup</a></strong>."
+msgstr ""
+"既存のデータベースと互換性の無い収集期間の形式が選択されました。<br /"
+"><strong><a href=\"%s\">バックアップのダウンロード</a></strong>"
+
+msgid ""
+"Choose \"Day of month\" to restart the accounting period monthly on a "
+"specific date, e.g. every 3rd. Choose \"Fixed interval\" to restart the "
+"accounting period exactly every N days, beginning at a given date."
+msgstr ""
+"月毎で設定した日付からのデータの計測を行うには、 \"月間\" を選択します(例: "
+"毎月3日)。設定した日数毎にデータの収集を行うには、\"特定の間隔\" を選択しま"
+"す。後者の場合、指定された日付から開始されます。"
+
+msgid "Commit interval"
+msgstr "コミット間隔"
+
+msgid "Compress database"
+msgstr "データベースの圧縮"
+
+msgid "Configuration"
+msgstr "設定"
+
+msgid "Conn."
+msgstr "接続数"
+
+msgid "Connections"
+msgstr "接続数"
+
+msgid "Connections / Host"
+msgstr "ホスト毎の接続数"
+
+msgid "Database directory"
+msgstr "データベース ディレクトリ"
+
+msgid ""
+"Database storage directory. One file per accounting period will be placed "
+"into this directory."
+msgstr ""
+"データベースの保存先ディレクトリです。計測期間あたり 1 つのファイルがこのディ"
+"レクトリに配置されます。"
+
+msgid "Day of month"
+msgstr "月間"
+
+msgid ""
+"Day of month to restart the accounting period. Use negative values to count "
+"towards the end of month, e.g. \"-5\" to specify the 27th of July or the "
+"24th of Februrary."
+msgstr ""
+"月の中で新たな収集期間を開始する日です。月の最終日からの日数をマイナス値で指"
+"定することができます(例: 7月27日または2月24日は \"-5\")。"
+
+msgid "Display"
+msgstr "表示"
+
+msgid "Down. (Bytes / Pkts.)"
+msgstr "ダウンロード(Bytes / Pkts.)"
+
+msgid "Download (Bytes / Packets)"
+msgstr "ダウンロード(Bytes / Packets)"
+
+msgid "Download / Application"
+msgstr "ダウンロード / アプリケーション"
+
+msgid "Download Database Backup"
+msgstr "データベース バックアップのダウンロード"
+
+msgid "Dualstack enabled hosts"
+msgstr "デュアルスタック ホスト"
+
+msgid "Due date"
+msgstr "期日"
+
+msgid "Export"
+msgstr "エクスポート"
+
+msgid "Family"
+msgstr "IP 種別"
+
+msgid "Fixed interval"
+msgstr "特定の間隔"
+
+msgid "Force reload…"
+msgstr "強制リロード..."
+
+msgid "General Settings"
+msgstr "全般設定"
+
+msgid "Generate Backup"
+msgstr "バックアップの作成"
+
+msgid "Host"
+msgstr "ホスト"
+
+msgid "Hostname: <big id=\"bubble-hostname\">example.org</big>"
+msgstr "ホスト名: <big id=\"bubble-hostname\">example.org</big>"
+
+msgid "IPv4 vs. IPv6"
+msgstr "IPv4 及び IPv6"
+
+msgid "IPv6"
+msgstr "IPv6"
+
+msgid "Interval"
+msgstr "間隔"
+
+msgid ""
+"Interval at which the temporary in-memory database is committed to the "
+"persistent database directory."
+msgstr ""
+"メモリー上の一時的なデータベースから、永続的なデータベース ディレクトリへのコ"
+"ミットを実行する間隔です。"
+
+msgid ""
+"Interval at which traffic counters of still established connections are "
+"refreshed from netlink information."
+msgstr ""
+
+msgid "Invalid or empty backup archive"
+msgstr "無効または空のバックアップ アーカイブです。"
+
+msgid "JSON dump"
+msgstr "JSON ダンプ"
+
+msgid "Length of accounting interval in days."
+msgstr "収集期間の日数です。"
+
+msgid "Local interfaces"
+msgstr "ローカル インターフェース"
+
+msgid "Local subnets"
+msgstr "ローカル サブネット"
+
+msgid "MAC"
+msgstr "MAC"
+
+msgid "Maximum entries"
+msgstr "最大件数"
+
+msgid ""
+"Maximum number of accounting periods to keep, use zero to keep databases "
+"forever."
+msgstr ""
+"計測データを保持する、収集期間の最大個数です。 '0' を設定した場合、全データを"
+"保持します。"
+
+msgid "Netlink Bandwidth Monitor"
+msgstr "Netlink Bandwidth Monitor"
+
+msgid "Netlink Bandwidth Monitor - Backup / Restore"
+msgstr "Netlink Bandwidth Monitor - バックアップ / 復元"
+
+msgid "Netlink Bandwidth Monitor - Configuration"
+msgstr "Netlink Bandwidth Monitor - 設定"
+
+msgid "No data recorded yet."
+msgstr "まだデータがありません。"
+
+msgid "Only conntrack streams from or to any of these networks are counted."
+msgstr ""
+"選択されたネットワークにおける conntrack ストリームのみが計測されます。"
+
+msgid "Only conntrack streams from or to any of these subnets are counted."
+msgstr "設定されたサブネットにおける conntrack ストリームのみが計測されます。"
+
+msgid "Preallocate database"
+msgstr "データベースの事前割当"
+
+msgid "Protocol"
+msgstr "プロトコル"
+
+msgid "Protocol Mapping"
+msgstr "プロトコル マッピング"
+
+msgid ""
+"Protocol mappings to distinguish traffic types per host, one mapping per "
+"line. The first value specifies the IP protocol, the second value the port "
+"number and the third column is the name of the mapped protocol."
+msgstr ""
+"ホスト毎のトラフィック形式を区別するためのプロトコル マッピングで、一行あたり"
+"一つのマッピングを追加します。各エントリーの一つ目の値は IP プロトコルを、2つ"
+"目の値はポート番号、3つ目はマッピングされたプロトコルの名前をそれぞれ表しま"
+"す。"
+
+msgid "Refresh interval"
+msgstr "リフレッシュ間隔"
+
+msgid "Restore"
+msgstr "復元"
+
+msgid "Restore Database Backup"
+msgstr "データベースの復元"
+
+msgid "Select accounting period:"
+msgstr "収集期間を選択:"
+
+msgid "Source IP"
+msgstr "アクセス元 IP"
+
+msgid "Start date"
+msgstr "開始日"
+
+msgid "Start date of the first accounting period, e.g. begin of ISP contract."
+msgstr "初回のデータ収集の開始日です(例: ISP 契約の開始日)。"
+
+msgid "Stored periods"
+msgstr "保存期間"
+
+msgid ""
+"The Netlink Bandwidth Monitor (nlbwmon) is a lightweight, efficient traffic "
+"accounting program keeping track of bandwidth usage per host and protocol."
+msgstr ""
+"Netlink Bandwidth Monitor (nlbwmon) は、軽量かつ、ホストやプロトコル毎に帯域"
+"幅使用量の追跡を行う効率的なトラフィック計測プログラムです。"
+
+msgid "The following database files have been restored: %s"
+msgstr "次のデータベース ファイルが復元されました: %s"
+
+msgid ""
+"The maximum amount of entries that should be put into the database, setting "
+"the limit to 0 will allow databases to grow indefinitely."
+msgstr ""
+"データベースに保管される最大件数です。 '0' を設定した場合、制限無しのデータ"
+"ベースの増大を許可します。"
+
+msgid "Traffic / Host"
+msgstr "トラフィック / ホスト"
+
+msgid "Traffic Distribution"
+msgstr "トラフィック内訳"
+
+msgid "Up. (Bytes / Pkts.)"
+msgstr "アップロード(Bytes / Pkts.)"
+
+msgid "Upload (Bytes / Packets)"
+msgstr "アップロード(Bytes / Packets)"
+
+msgid "Upload / Application"
+msgstr "アップロード / アプリケーション"
+
+msgid "Vendor: <big id=\"bubble-vendor\">Example Corp.</big>"
+msgstr "ベンダ: <big id=\"bubble-vendor\">Example Corp.</big>"
+
+msgid "Warning"
+msgstr "警告"
+
+msgid ""
+"Whether to gzip compress archive databases. Compressing the database files "
+"makes accessing old data slightly slower but helps to reduce storage "
+"requirements."
+msgstr ""
+"データベースの gzip 圧縮アーカイブ化です。データベース ファイルを圧縮すると古"
+"いデータへのアクセスが多少遅くなりますが、ストレージ使用量の低減に役立ちま"
+"す。"
+
+msgid ""
+"Whether to preallocate the maximum possible database size in memory. This is "
+"mainly useful for memory constrained systems which might not be able to "
+"satisfy memory allocation after longer uptime periods."
+msgstr ""
+
+msgid "no traffic"
+msgstr "トラフィック無し"
+
+msgid "other"
+msgstr "その他"
diff --git a/applications/luci-app-nlbwmon/po/templates/nlbwmon.pot b/applications/luci-app-nlbwmon/po/templates/nlbwmon.pot
new file mode 100644 (file)
index 0000000..61d2230
--- /dev/null
@@ -0,0 +1,352 @@
+msgid ""
+msgstr "Content-Type: text/plain; charset=UTF-8"
+
+msgid "%d IPv4-only hosts"
+msgstr ""
+
+msgid "%d IPv6-only hosts"
+msgstr ""
+
+msgid "%d dual-stack hosts"
+msgstr ""
+
+msgid "%s and %s"
+msgstr ""
+
+msgid "%s, %s and %s"
+msgstr ""
+
+msgid "-1 - Restart every last day of month"
+msgstr ""
+
+msgid "-7 - Restart a week before end of month"
+msgstr ""
+
+msgid "1 - Restart every 1st of month"
+msgstr ""
+
+msgid "10m - frequent commits at the expense of flash wear"
+msgstr ""
+
+msgid "12h - compromise between risk of data loss and flash wear"
+msgstr ""
+
+msgid "24h - least flash wear at the expense of data loss risk"
+msgstr ""
+
+msgid "30s - refresh twice per minute for reasonably current stats"
+msgstr ""
+
+msgid "5m - rarely refresh to avoid frequently clearing conntrack counters"
+msgstr ""
+
+msgid "60s - commit minutely, useful for non-flash storage"
+msgstr ""
+
+msgid "<big id=\"conn-total\">0</big> connections"
+msgstr ""
+
+msgid "<big id=\"host-total\">0</big> hosts"
+msgstr ""
+
+msgid "<big id=\"ipv6-hosts\">0%</big> IPv6 support rate among hosts"
+msgstr ""
+
+msgid "<big id=\"ipv6-rx\">0B</big> total IPv6 download"
+msgstr ""
+
+msgid "<big id=\"ipv6-share\">0%</big> of the total traffic is IPv6"
+msgstr ""
+
+msgid "<big id=\"ipv6-tx\">0B</big> total IPv6 upload"
+msgstr ""
+
+msgid "<big id=\"layer7-most-conn\">0</big> cause the most connections"
+msgstr ""
+
+msgid "<big id=\"layer7-most-rx\">0</big> cause the most download"
+msgstr ""
+
+msgid "<big id=\"layer7-most-tx\">0</big> cause the most upload"
+msgstr ""
+
+msgid "<big id=\"layer7-total\">0</big> different application protocols"
+msgstr ""
+
+msgid "<big id=\"rx-total\">0</big> download"
+msgstr ""
+
+msgid "<big id=\"tx-total\">0</big> upload"
+msgstr ""
+
+msgid "Accounting period"
+msgstr ""
+
+msgid "Advanced Settings"
+msgstr ""
+
+msgid "Application"
+msgstr ""
+
+msgid "Application Protocols"
+msgstr ""
+
+msgid "Backup"
+msgstr ""
+
+msgid "Bandwidth Monitor"
+msgstr ""
+
+msgid "CSV, grouped by IP"
+msgstr ""
+
+msgid "CSV, grouped by MAC"
+msgstr ""
+
+msgid "CSV, grouped by protocol"
+msgstr ""
+
+msgid ""
+"Changing the accounting interval type will invalidate existing databases!"
+"<br /><strong><a href=\"%s\">Download backup</a></strong>."
+msgstr ""
+
+msgid ""
+"Choose \"Day of month\" to restart the accounting period monthly on a "
+"specific date, e.g. every 3rd. Choose \"Fixed interval\" to restart the "
+"accounting period exactly every N days, beginning at a given date."
+msgstr ""
+
+msgid "Commit interval"
+msgstr ""
+
+msgid "Compress database"
+msgstr ""
+
+msgid "Configuration"
+msgstr ""
+
+msgid "Conn."
+msgstr ""
+
+msgid "Connections"
+msgstr ""
+
+msgid "Connections / Host"
+msgstr ""
+
+msgid "Database directory"
+msgstr ""
+
+msgid ""
+"Database storage directory. One file per accounting period will be placed "
+"into this directory."
+msgstr ""
+
+msgid "Day of month"
+msgstr ""
+
+msgid ""
+"Day of month to restart the accounting period. Use negative values to count "
+"towards the end of month, e.g. \"-5\" to specify the 27th of July or the "
+"24th of Februrary."
+msgstr ""
+
+msgid "Display"
+msgstr ""
+
+msgid "Down. (Bytes / Pkts.)"
+msgstr ""
+
+msgid "Download (Bytes / Packets)"
+msgstr ""
+
+msgid "Download / Application"
+msgstr ""
+
+msgid "Download Database Backup"
+msgstr ""
+
+msgid "Dualstack enabled hosts"
+msgstr ""
+
+msgid "Due date"
+msgstr ""
+
+msgid "Export"
+msgstr ""
+
+msgid "Family"
+msgstr ""
+
+msgid "Fixed interval"
+msgstr ""
+
+msgid "Force reload…"
+msgstr ""
+
+msgid "General Settings"
+msgstr ""
+
+msgid "Generate Backup"
+msgstr ""
+
+msgid "Host"
+msgstr ""
+
+msgid "Hostname: <big id=\"bubble-hostname\">example.org</big>"
+msgstr ""
+
+msgid "IPv4 vs. IPv6"
+msgstr ""
+
+msgid "IPv6"
+msgstr ""
+
+msgid "Interval"
+msgstr ""
+
+msgid ""
+"Interval at which the temporary in-memory database is committed to the "
+"persistent database directory."
+msgstr ""
+
+msgid ""
+"Interval at which traffic counters of still established connections are "
+"refreshed from netlink information."
+msgstr ""
+
+msgid "Invalid or empty backup archive"
+msgstr ""
+
+msgid "JSON dump"
+msgstr ""
+
+msgid "Length of accounting interval in days."
+msgstr ""
+
+msgid "Local interfaces"
+msgstr ""
+
+msgid "Local subnets"
+msgstr ""
+
+msgid "MAC"
+msgstr ""
+
+msgid "Maximum entries"
+msgstr ""
+
+msgid ""
+"Maximum number of accounting periods to keep, use zero to keep databases "
+"forever."
+msgstr ""
+
+msgid "Netlink Bandwidth Monitor"
+msgstr ""
+
+msgid "Netlink Bandwidth Monitor - Backup / Restore"
+msgstr ""
+
+msgid "Netlink Bandwidth Monitor - Configuration"
+msgstr ""
+
+msgid "No data recorded yet."
+msgstr ""
+
+msgid "Only conntrack streams from or to any of these networks are counted."
+msgstr ""
+
+msgid "Only conntrack streams from or to any of these subnets are counted."
+msgstr ""
+
+msgid "Preallocate database"
+msgstr ""
+
+msgid "Protocol"
+msgstr ""
+
+msgid "Protocol Mapping"
+msgstr ""
+
+msgid ""
+"Protocol mappings to distinguish traffic types per host, one mapping per "
+"line. The first value specifies the IP protocol, the second value the port "
+"number and the third column is the name of the mapped protocol."
+msgstr ""
+
+msgid "Refresh interval"
+msgstr ""
+
+msgid "Restore"
+msgstr ""
+
+msgid "Restore Database Backup"
+msgstr ""
+
+msgid "Select accounting period:"
+msgstr ""
+
+msgid "Source IP"
+msgstr ""
+
+msgid "Start date"
+msgstr ""
+
+msgid "Start date of the first accounting period, e.g. begin of ISP contract."
+msgstr ""
+
+msgid "Stored periods"
+msgstr ""
+
+msgid ""
+"The Netlink Bandwidth Monitor (nlbwmon) is a lightweight, efficient traffic "
+"accounting program keeping track of bandwidth usage per host and protocol."
+msgstr ""
+
+msgid "The following database files have been restored: %s"
+msgstr ""
+
+msgid ""
+"The maximum amount of entries that should be put into the database, setting "
+"the limit to 0 will allow databases to grow indefinitely."
+msgstr ""
+
+msgid "Traffic / Host"
+msgstr ""
+
+msgid "Traffic Distribution"
+msgstr ""
+
+msgid "Up. (Bytes / Pkts.)"
+msgstr ""
+
+msgid "Upload (Bytes / Packets)"
+msgstr ""
+
+msgid "Upload / Application"
+msgstr ""
+
+msgid "Vendor: <big id=\"bubble-vendor\">Example Corp.</big>"
+msgstr ""
+
+msgid "Warning"
+msgstr ""
+
+msgid ""
+"Whether to gzip compress archive databases. Compressing the database files "
+"makes accessing old data slightly slower but helps to reduce storage "
+"requirements."
+msgstr ""
+
+msgid ""
+"Whether to preallocate the maximum possible database size in memory. This is "
+"mainly useful for memory constrained systems which might not be able to "
+"satisfy memory allocation after longer uptime periods."
+msgstr ""
+
+msgid "no traffic"
+msgstr ""
+
+msgid "other"
+msgstr ""
diff --git a/applications/luci-app-nlbwmon/root/etc/uci-defaults/40_luci-nlbwmon b/applications/luci-app-nlbwmon/root/etc/uci-defaults/40_luci-nlbwmon
new file mode 100644 (file)
index 0000000..c977177
--- /dev/null
@@ -0,0 +1,11 @@
+#!/bin/sh
+
+uci -q batch <<-EOF >/dev/null
+       delete ucitrack.@nlbwmon[-1]
+       add ucitrack nlbwmon
+       set ucitrack.@nlbwmon[-1].init=nlbwmon
+       commit ucitrack
+EOF
+
+rm -f /tmp/luci-indexcache
+exit 0
index 2753f45..b11890f 100644 (file)
@@ -108,6 +108,7 @@ function options_common(s, tab)
        s:taboption(tab, Value, "user", translate("Run as"))
 
        s:taboption(tab, Flag, "verbose", translate("Verbose"))
+       s:taboption(tab, Flag, "ipv6_first", translate("IPv6 First"), translate("Prefer IPv6 addresses when resolving names"))
        s:taboption(tab, Flag, "fast_open", translate("Enable TCP Fast Open"))
        s:taboption(tab, Flag, "reuse_port", translate("Enable SO_REUSEPORT"))
 end
@@ -205,6 +206,7 @@ names_options_client = {
 
 names_options_common = {
        "verbose",
+       "ipv6_first",
        "fast_open",
        "reuse_port",
        "mode",
@@ -247,7 +249,4 @@ methods = {
        "salsa20",
        "chacha20",
        "chacha20-ietf",
-       "aes-128-gcm",
-       "aes-192-gcm",
-       "aes-256-gcm",
 }
index 33d5051..738af55 100644 (file)
@@ -15,6 +15,12 @@ msgstr ""
 "Plural-Forms: nplurals=2; plural=(n != 1);\n"
 "X-Generator: Pootle 2.0.6\n"
 
+msgid "APC UPS"
+msgstr ""
+
+msgid "APCUPS Plugin Configuration"
+msgstr ""
+
 msgid "Action (target)"
 msgstr "Acció (objectiu)"
 
@@ -295,6 +301,9 @@ msgstr "Monitoritza els discs i les particions"
 msgid "Monitor filesystem types"
 msgstr "Monitoritza els tipus de sistema de fitxers"
 
+msgid "Monitor host"
+msgstr ""
+
 msgid "Monitor hosts"
 msgstr "Monitoritza màquines"
 
@@ -381,6 +390,9 @@ msgstr "Configuració del connector ping"
 msgid "Port"
 msgstr "Port"
 
+msgid "Port for apcupsd communication"
+msgstr ""
+
 msgid "Processes"
 msgstr "Processos"
 
@@ -506,6 +518,9 @@ msgstr "TTL per paquets ping"
 msgid "Table"
 msgstr "Taula"
 
+msgid "The APCUPS plugin collects statistics about the APC UPS."
+msgstr ""
+
 msgid "The NUT plugin reads information about Uninterruptible Power Supplies."
 msgstr ""
 
index 8498316..fc3f513 100644 (file)
@@ -11,6 +11,12 @@ msgstr ""
 "Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
 "X-Generator: Pootle 2.0.6\n"
 
+msgid "APC UPS"
+msgstr ""
+
+msgid "APCUPS Plugin Configuration"
+msgstr ""
+
 msgid "Action (target)"
 msgstr "Akce (cíl)"
 
@@ -290,6 +296,9 @@ msgstr "Sledovat disky a oddíly"
 msgid "Monitor filesystem types"
 msgstr "Sledovat typy souborových systémů"
 
+msgid "Monitor host"
+msgstr ""
+
 msgid "Monitor hosts"
 msgstr "Sledovat hostitele"
 
@@ -376,6 +385,9 @@ msgstr "Nastavení pluginu Ping"
 msgid "Port"
 msgstr "Port"
 
+msgid "Port for apcupsd communication"
+msgstr ""
+
 msgid "Processes"
 msgstr "Procesy"
 
@@ -500,6 +512,9 @@ msgstr "TTL pro pakety pingu"
 msgid "Table"
 msgstr ""
 
+msgid "The APCUPS plugin collects statistics about the APC UPS."
+msgstr ""
+
 msgid "The NUT plugin reads information about Uninterruptible Power Supplies."
 msgstr ""
 
index 196433e..45ba020 100644 (file)
@@ -13,6 +13,12 @@ msgstr ""
 "Plural-Forms: nplurals=2; plural=(n != 1);\n"
 "X-Generator: Pootle 2.0.6\n"
 
+msgid "APC UPS"
+msgstr ""
+
+msgid "APCUPS Plugin Configuration"
+msgstr ""
+
 msgid "Action (target)"
 msgstr "Aktion (Ziel)"
 
@@ -297,6 +303,9 @@ msgstr "Geräte und Partitionen überwachen"
 msgid "Monitor filesystem types"
 msgstr "Datesystemtypen überwachen"
 
+msgid "Monitor host"
+msgstr ""
+
 msgid "Monitor hosts"
 msgstr "Hosts überwachen"
 
@@ -383,6 +392,9 @@ msgstr "Ping Plugin Konfiguration"
 msgid "Port"
 msgstr "Port"
 
+msgid "Port for apcupsd communication"
+msgstr ""
+
 msgid "Processes"
 msgstr "Prozesse"
 
@@ -506,6 +518,9 @@ msgstr "TTL für Ping Pakete"
 msgid "Table"
 msgstr "Tabelle"
 
+msgid "The APCUPS plugin collects statistics about the APC UPS."
+msgstr ""
+
 msgid "The NUT plugin reads information about Uninterruptible Power Supplies."
 msgstr ""
 "Das NUT-Plugin liest Informationen über Unterbrechungsfreie Stromversorgungen"
index da54cda..4062868 100644 (file)
@@ -13,6 +13,12 @@ msgstr ""
 "Plural-Forms: nplurals=2; plural=(n != 1);\n"
 "X-Generator: Pootle 2.0.4\n"
 
+msgid "APC UPS"
+msgstr ""
+
+msgid "APCUPS Plugin Configuration"
+msgstr ""
+
 msgid "Action (target)"
 msgstr ""
 
@@ -288,6 +294,9 @@ msgstr ""
 msgid "Monitor filesystem types"
 msgstr ""
 
+msgid "Monitor host"
+msgstr ""
+
 msgid "Monitor hosts"
 msgstr ""
 
@@ -374,6 +383,9 @@ msgstr ""
 msgid "Port"
 msgstr ""
 
+msgid "Port for apcupsd communication"
+msgstr ""
+
 msgid "Processes"
 msgstr "Διεργασίες"
 
@@ -497,6 +509,9 @@ msgstr ""
 msgid "Table"
 msgstr "Πίνακας"
 
+msgid "The APCUPS plugin collects statistics about the APC UPS."
+msgstr ""
+
 msgid "The NUT plugin reads information about Uninterruptible Power Supplies."
 msgstr ""
 
index d9ab59c..f7ebfe0 100644 (file)
@@ -13,6 +13,12 @@ msgstr ""
 "Content-Transfer-Encoding: 8bit\n"
 "X-Generator: Translate Toolkit 1.1.1\n"
 
+msgid "APC UPS"
+msgstr ""
+
+msgid "APCUPS Plugin Configuration"
+msgstr ""
+
 msgid "Action (target)"
 msgstr "Action (target)"
 
@@ -293,6 +299,9 @@ msgstr "Monitor disks and partitions"
 msgid "Monitor filesystem types"
 msgstr "Monitor filesystem types"
 
+msgid "Monitor host"
+msgstr ""
+
 msgid "Monitor hosts"
 msgstr "Monitor hosts"
 
@@ -379,6 +388,9 @@ msgstr "Ping Plugin Configuration"
 msgid "Port"
 msgstr ""
 
+msgid "Port for apcupsd communication"
+msgstr ""
+
 msgid "Processes"
 msgstr "Processes"
 
@@ -502,6 +514,9 @@ msgstr "TTL for ping packets"
 msgid "Table"
 msgstr "Table"
 
+msgid "The APCUPS plugin collects statistics about the APC UPS."
+msgstr ""
+
 msgid "The NUT plugin reads information about Uninterruptible Power Supplies."
 msgstr ""
 
index 18c2581..3c811ff 100644 (file)
@@ -13,6 +13,12 @@ msgstr ""
 "Plural-Forms: nplurals=2; plural=(n != 1);\n"
 "X-Generator: Pootle 2.0.6\n"
 
+msgid "APC UPS"
+msgstr ""
+
+msgid "APCUPS Plugin Configuration"
+msgstr ""
+
 msgid "Action (target)"
 msgstr "Acción (objetivo)"
 
@@ -292,6 +298,9 @@ msgstr "Monitorizar discos y particiones"
 msgid "Monitor filesystem types"
 msgstr "Monitorizar tipos de sistema de archivos"
 
+msgid "Monitor host"
+msgstr ""
+
 msgid "Monitor hosts"
 msgstr "Monitorizar máquinas"
 
@@ -378,6 +387,9 @@ msgstr "Configuración del plugin \"Ping\""
 msgid "Port"
 msgstr "Puerto"
 
+msgid "Port for apcupsd communication"
+msgstr ""
+
 msgid "Processes"
 msgstr "Procesos"
 
@@ -501,6 +513,9 @@ msgstr "TTL para paquetes de ping"
 msgid "Table"
 msgstr "Tabla"
 
+msgid "The APCUPS plugin collects statistics about the APC UPS."
+msgstr ""
+
 msgid "The NUT plugin reads information about Uninterruptible Power Supplies."
 msgstr ""
 "El plugin NUT obtiene información sobre Sistemas de Alimentación "
index b657bd3..bc156dd 100644 (file)
@@ -13,6 +13,12 @@ msgstr ""
 "Plural-Forms: nplurals=2; plural=(n > 1);\n"
 "X-Generator: Pootle 2.0.4\n"
 
+msgid "APC UPS"
+msgstr ""
+
+msgid "APCUPS Plugin Configuration"
+msgstr ""
+
 msgid "Action (target)"
 msgstr "Action (cible)"
 
@@ -294,6 +300,9 @@ msgstr "Disques et partitions à surveiller"
 msgid "Monitor filesystem types"
 msgstr "types de systèmes de fichier à surveiller"
 
+msgid "Monitor host"
+msgstr ""
+
 msgid "Monitor hosts"
 msgstr "Hôtes à surveiller"
 
@@ -380,6 +389,9 @@ msgstr "Configuration du greffon Ping"
 msgid "Port"
 msgstr ""
 
+msgid "Port for apcupsd communication"
+msgstr ""
+
 msgid "Processes"
 msgstr "Processus"
 
@@ -503,6 +515,9 @@ msgstr "TTL des paquets ping"
 msgid "Table"
 msgstr "Table"
 
+msgid "The APCUPS plugin collects statistics about the APC UPS."
+msgstr ""
+
 msgid "The NUT plugin reads information about Uninterruptible Power Supplies."
 msgstr ""
 
index 6f40a47..35f978e 100644 (file)
@@ -13,6 +13,12 @@ msgstr ""
 "Plural-Forms: nplurals=2; plural=(n != 1);\n"
 "X-Generator: Pootle 2.0.6\n"
 
+msgid "APC UPS"
+msgstr ""
+
+msgid "APCUPS Plugin Configuration"
+msgstr ""
+
 msgid "Action (target)"
 msgstr ""
 
@@ -283,6 +289,9 @@ msgstr ""
 msgid "Monitor filesystem types"
 msgstr ""
 
+msgid "Monitor host"
+msgstr ""
+
 msgid "Monitor hosts"
 msgstr ""
 
@@ -369,6 +378,9 @@ msgstr ""
 msgid "Port"
 msgstr ""
 
+msgid "Port for apcupsd communication"
+msgstr ""
+
 msgid "Processes"
 msgstr ""
 
@@ -492,6 +504,9 @@ msgstr ""
 msgid "Table"
 msgstr ""
 
+msgid "The APCUPS plugin collects statistics about the APC UPS."
+msgstr ""
+
 msgid "The NUT plugin reads information about Uninterruptible Power Supplies."
 msgstr ""
 
index 979c72f..e5c4e60 100644 (file)
@@ -11,6 +11,12 @@ msgstr ""
 "Plural-Forms: nplurals=2; plural=(n != 1);\n"
 "X-Generator: Pootle 2.0.6\n"
 
+msgid "APC UPS"
+msgstr ""
+
+msgid "APCUPS Plugin Configuration"
+msgstr ""
+
 msgid "Action (target)"
 msgstr "Tevékenység (cél)"
 
@@ -295,6 +301,9 @@ msgstr "Lemezek és partíciók figyelése"
 msgid "Monitor filesystem types"
 msgstr "Fájlrendszer típusok figyelése"
 
+msgid "Monitor host"
+msgstr ""
+
 msgid "Monitor hosts"
 msgstr "Gépek figyelése"
 
@@ -381,6 +390,9 @@ msgstr "Ping bővítmény beállítása"
 msgid "Port"
 msgstr "Port"
 
+msgid "Port for apcupsd communication"
+msgstr ""
+
 msgid "Processes"
 msgstr "Folyamatok"
 
@@ -508,6 +520,9 @@ msgstr "TTL a ping csomagokhoz"
 msgid "Table"
 msgstr "Táblázat"
 
+msgid "The APCUPS plugin collects statistics about the APC UPS."
+msgstr ""
+
 msgid "The NUT plugin reads information about Uninterruptible Power Supplies."
 msgstr "A NUT bővítmény a szünetmentes tápokról ad információkat."
 
index b0ae3d6..2451503 100644 (file)
@@ -13,6 +13,12 @@ msgstr ""
 "Plural-Forms: nplurals=2; plural=(n != 1);\n"
 "X-Generator: Pootle 2.0.6\n"
 
+msgid "APC UPS"
+msgstr ""
+
+msgid "APCUPS Plugin Configuration"
+msgstr ""
+
 msgid "Action (target)"
 msgstr "Azione (destinazione)"
 
@@ -293,6 +299,9 @@ msgstr ""
 msgid "Monitor filesystem types"
 msgstr ""
 
+msgid "Monitor host"
+msgstr ""
+
 msgid "Monitor hosts"
 msgstr ""
 
@@ -379,6 +388,9 @@ msgstr ""
 msgid "Port"
 msgstr ""
 
+msgid "Port for apcupsd communication"
+msgstr ""
+
 msgid "Processes"
 msgstr ""
 
@@ -502,6 +514,9 @@ msgstr ""
 msgid "Table"
 msgstr "Tabella"
 
+msgid "The APCUPS plugin collects statistics about the APC UPS."
+msgstr ""
+
 msgid "The NUT plugin reads information about Uninterruptible Power Supplies."
 msgstr ""
 
index 690d920..53941cf 100644 (file)
@@ -13,6 +13,12 @@ msgstr ""
 "X-Generator: Poedit 1.8.11\n"
 "Language-Team: \n"
 
+msgid "APC UPS"
+msgstr ""
+
+msgid "APCUPS Plugin Configuration"
+msgstr ""
+
 msgid "Action (target)"
 msgstr "アクション(対象)"
 
@@ -295,6 +301,9 @@ msgstr "ディスクとパーティションをモニターする"
 msgid "Monitor filesystem types"
 msgstr "ファイルシステム タイプをモニターする"
 
+msgid "Monitor host"
+msgstr ""
+
 msgid "Monitor hosts"
 msgstr "ホストをモニターする"
 
@@ -384,6 +393,9 @@ msgstr "Ping プラグイン設定"
 msgid "Port"
 msgstr "ポート"
 
+msgid "Port for apcupsd communication"
+msgstr ""
+
 msgid "Processes"
 msgstr "プロセス"
 
@@ -507,6 +519,9 @@ msgstr "pingパケットのTTL"
 msgid "Table"
 msgstr "テーブル"
 
+msgid "The APCUPS plugin collects statistics about the APC UPS."
+msgstr ""
+
 msgid "The NUT plugin reads information about Uninterruptible Power Supplies."
 msgstr "NUT プラグインは、無停電電源装置についての情報を読み取ります。"
 
index 582314c..c02556f 100644 (file)
@@ -10,6 +10,12 @@ msgstr ""
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
 
+msgid "APC UPS"
+msgstr ""
+
+msgid "APCUPS Plugin Configuration"
+msgstr ""
+
 msgid "Action (target)"
 msgstr ""
 
@@ -280,6 +286,9 @@ msgstr ""
 msgid "Monitor filesystem types"
 msgstr ""
 
+msgid "Monitor host"
+msgstr ""
+
 msgid "Monitor hosts"
 msgstr ""
 
@@ -366,6 +375,9 @@ msgstr ""
 msgid "Port"
 msgstr ""
 
+msgid "Port for apcupsd communication"
+msgstr ""
+
 msgid "Processes"
 msgstr ""
 
@@ -489,6 +501,9 @@ msgstr ""
 msgid "Table"
 msgstr ""
 
+msgid "The APCUPS plugin collects statistics about the APC UPS."
+msgstr ""
+
 msgid "The NUT plugin reads information about Uninterruptible Power Supplies."
 msgstr ""
 
index d37bc48..4de2ee6 100644 (file)
@@ -4,6 +4,12 @@ msgstr ""
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
 
+msgid "APC UPS"
+msgstr ""
+
+msgid "APCUPS Plugin Configuration"
+msgstr ""
+
 msgid "Action (target)"
 msgstr "Handling (mål)"
 
@@ -282,6 +288,9 @@ msgstr "Overvåk disker og partisjoner"
 msgid "Monitor filesystem types"
 msgstr "Overvåk filsystem typer"
 
+msgid "Monitor host"
+msgstr ""
+
 msgid "Monitor hosts"
 msgstr "Overvåk verter"
 
@@ -368,6 +377,9 @@ msgstr "Ping plugin konfigurasjon"
 msgid "Port"
 msgstr ""
 
+msgid "Port for apcupsd communication"
+msgstr ""
+
 msgid "Processes"
 msgstr "Prosesser"
 
@@ -491,6 +503,9 @@ msgstr "TTL for ping pakker"
 msgid "Table"
 msgstr "Tabell"
 
+msgid "The APCUPS plugin collects statistics about the APC UPS."
+msgstr ""
+
 msgid "The NUT plugin reads information about Uninterruptible Power Supplies."
 msgstr ""
 
index bf2ec93..6e34ce0 100644 (file)
@@ -14,6 +14,12 @@ msgstr ""
 "|| n%100>=20) ? 1 : 2);\n"
 "X-Generator: Pootle 2.0.6\n"
 
+msgid "APC UPS"
+msgstr ""
+
+msgid "APCUPS Plugin Configuration"
+msgstr ""
+
 msgid "Action (target)"
 msgstr "Akcja (cel)"
 
@@ -296,6 +302,9 @@ msgstr "Monitoruj dyski i partycje"
 msgid "Monitor filesystem types"
 msgstr "Monitoruj system plików"
 
+msgid "Monitor host"
+msgstr ""
+
 msgid "Monitor hosts"
 msgstr "Monitoruj hosty"
 
@@ -382,6 +391,9 @@ msgstr "Konfiguracja wtyczki Ping"
 msgid "Port"
 msgstr "Port"
 
+msgid "Port for apcupsd communication"
+msgstr ""
+
 msgid "Processes"
 msgstr "Procesy"
 
@@ -506,6 +518,9 @@ msgstr "TTL dla pakietów ping"
 msgid "Table"
 msgstr "Tabela"
 
+msgid "The APCUPS plugin collects statistics about the APC UPS."
+msgstr ""
+
 msgid "The NUT plugin reads information about Uninterruptible Power Supplies."
 msgstr "Wtyczka Nut Informuje o Nie przerywalnym Zasilaniu"
 
index 74c4a26..c5d6899 100644 (file)
@@ -13,6 +13,12 @@ msgstr ""
 "X-Generator: Poedit 1.8.11\n"
 "Language-Team: \n"
 
+msgid "APC UPS"
+msgstr ""
+
+msgid "APCUPS Plugin Configuration"
+msgstr ""
+
 msgid "Action (target)"
 msgstr "Ação (destino)"
 
@@ -299,6 +305,9 @@ msgstr "Monitoras discos e partições"
 msgid "Monitor filesystem types"
 msgstr "Monitorar tipos de sistemas de arquivos"
 
+msgid "Monitor host"
+msgstr ""
+
 msgid "Monitor hosts"
 msgstr "Monitorar os equipamentos"
 
@@ -388,6 +397,9 @@ msgstr "Configuração do plugin Ping"
 msgid "Port"
 msgstr "Porta"
 
+msgid "Port for apcupsd communication"
+msgstr ""
+
 msgid "Processes"
 msgstr "Processos"
 
@@ -511,6 +523,9 @@ msgstr "TTL para os pacotes do ping"
 msgid "Table"
 msgstr "Tabela"
 
+msgid "The APCUPS plugin collects statistics about the APC UPS."
+msgstr ""
+
 msgid "The NUT plugin reads information about Uninterruptible Power Supplies."
 msgstr "O plugin NUT lê informações sobre Fontes de alimentação ininterruptas."
 
index 79c7bd0..245e6e9 100644 (file)
@@ -13,6 +13,12 @@ msgstr ""
 "Plural-Forms: nplurals=2; plural=(n != 1);\n"
 "X-Generator: Pootle 2.0.6\n"
 
+msgid "APC UPS"
+msgstr ""
+
+msgid "APCUPS Plugin Configuration"
+msgstr ""
+
 msgid "Action (target)"
 msgstr "Ação (destino)"
 
@@ -295,6 +301,9 @@ msgstr "Monitoras discos e partições"
 msgid "Monitor filesystem types"
 msgstr "Monitorar tipos de sistemas de arquivos"
 
+msgid "Monitor host"
+msgstr ""
+
 msgid "Monitor hosts"
 msgstr "Monitorar os hosts"
 
@@ -381,6 +390,9 @@ msgstr "Configuração do plugin Ping"
 msgid "Port"
 msgstr "Porta"
 
+msgid "Port for apcupsd communication"
+msgstr ""
+
 msgid "Processes"
 msgstr "Processos"
 
@@ -504,6 +516,9 @@ msgstr "TTL para os pacotes do ping"
 msgid "Table"
 msgstr "Tabela"
 
+msgid "The APCUPS plugin collects statistics about the APC UPS."
+msgstr ""
+
 msgid "The NUT plugin reads information about Uninterruptible Power Supplies."
 msgstr ""
 
index c5dfcfe..a326fec 100644 (file)
@@ -14,6 +14,12 @@ msgstr ""
 "20)) ? 1 : 2);;\n"
 "X-Generator: Pootle 2.0.4\n"
 
+msgid "APC UPS"
+msgstr ""
+
+msgid "APCUPS Plugin Configuration"
+msgstr ""
+
 msgid "Action (target)"
 msgstr ""
 
@@ -287,6 +293,9 @@ msgstr ""
 msgid "Monitor filesystem types"
 msgstr ""
 
+msgid "Monitor host"
+msgstr ""
+
 msgid "Monitor hosts"
 msgstr ""
 
@@ -373,6 +382,9 @@ msgstr ""
 msgid "Port"
 msgstr ""
 
+msgid "Port for apcupsd communication"
+msgstr ""
+
 msgid "Processes"
 msgstr "Procese"
 
@@ -496,6 +508,9 @@ msgstr ""
 msgid "Table"
 msgstr ""
 
+msgid "The APCUPS plugin collects statistics about the APC UPS."
+msgstr ""
+
 msgid "The NUT plugin reads information about Uninterruptible Power Supplies."
 msgstr ""
 
index 3a418de..9d0ff9f 100644 (file)
@@ -15,6 +15,12 @@ msgstr ""
 "X-Generator: Pootle 2.0.6\n"
 "X-Poedit-SourceCharset: UTF-8\n"
 
+msgid "APC UPS"
+msgstr ""
+
+msgid "APCUPS Plugin Configuration"
+msgstr ""
+
 msgid "Action (target)"
 msgstr "Действие (цель)"
 
@@ -297,6 +303,9 @@ msgstr "Собирать статистику с дисков и раздело
 msgid "Monitor filesystem types"
 msgstr "Собирать статистику с файловых систем"
 
+msgid "Monitor host"
+msgstr ""
+
 msgid "Monitor hosts"
 msgstr "Собирать статистику с хостов"
 
@@ -383,6 +392,9 @@ msgstr "Конфигурация модуля Ping"
 msgid "Port"
 msgstr "Порт"
 
+msgid "Port for apcupsd communication"
+msgstr ""
+
 msgid "Processes"
 msgstr "Процессы"
 
@@ -508,6 +520,9 @@ msgstr "TTL для ping-пакетов"
 msgid "Table"
 msgstr "Таблица"
 
+msgid "The APCUPS plugin collects statistics about the APC UPS."
+msgstr ""
+
 msgid "The NUT plugin reads information about Uninterruptible Power Supplies."
 msgstr ""
 
index 6dba7d0..53858ca 100644 (file)
@@ -8,6 +8,12 @@ msgstr ""
 "Content-Transfer-Encoding: 8bit\n"
 "Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
 
+msgid "APC UPS"
+msgstr ""
+
+msgid "APCUPS Plugin Configuration"
+msgstr ""
+
 msgid "Action (target)"
 msgstr ""
 
@@ -278,6 +284,9 @@ msgstr ""
 msgid "Monitor filesystem types"
 msgstr ""
 
+msgid "Monitor host"
+msgstr ""
+
 msgid "Monitor hosts"
 msgstr ""
 
@@ -364,6 +373,9 @@ msgstr ""
 msgid "Port"
 msgstr ""
 
+msgid "Port for apcupsd communication"
+msgstr ""
+
 msgid "Processes"
 msgstr ""
 
@@ -487,6 +499,9 @@ msgstr ""
 msgid "Table"
 msgstr ""
 
+msgid "The APCUPS plugin collects statistics about the APC UPS."
+msgstr ""
+
 msgid "The NUT plugin reads information about Uninterruptible Power Supplies."
 msgstr ""
 
index bef0f2d..9d738f2 100644 (file)
@@ -9,6 +9,12 @@ msgstr ""
 "Content-Transfer-Encoding: 8bit\n"
 "Plural-Forms: nplurals=2; plural=(n != 1);\n"
 
+msgid "APC UPS"
+msgstr ""
+
+msgid "APCUPS Plugin Configuration"
+msgstr ""
+
 msgid "Action (target)"
 msgstr ""
 
@@ -283,6 +289,9 @@ msgstr "Övervaka hårddiskar och partitioner"
 msgid "Monitor filesystem types"
 msgstr "Övervaka filsystemtyper"
 
+msgid "Monitor host"
+msgstr ""
+
 msgid "Monitor hosts"
 msgstr "Övervaka värdar"
 
@@ -369,6 +378,9 @@ msgstr ""
 msgid "Port"
 msgstr "Port"
 
+msgid "Port for apcupsd communication"
+msgstr ""
+
 msgid "Processes"
 msgstr "Processer"
 
@@ -492,6 +504,9 @@ msgstr "TTL för ping-paket"
 msgid "Table"
 msgstr "Tabell"
 
+msgid "The APCUPS plugin collects statistics about the APC UPS."
+msgstr ""
+
 msgid "The NUT plugin reads information about Uninterruptible Power Supplies."
 msgstr ""
 
index c57a85b..ec630b6 100644 (file)
@@ -1,6 +1,12 @@
 msgid ""
 msgstr "Content-Type: text/plain; charset=UTF-8"
 
+msgid "APC UPS"
+msgstr ""
+
+msgid "APCUPS Plugin Configuration"
+msgstr ""
+
 msgid "Action (target)"
 msgstr ""
 
@@ -271,6 +277,9 @@ msgstr ""
 msgid "Monitor filesystem types"
 msgstr ""
 
+msgid "Monitor host"
+msgstr ""
+
 msgid "Monitor hosts"
 msgstr ""
 
@@ -357,6 +366,9 @@ msgstr ""
 msgid "Port"
 msgstr ""
 
+msgid "Port for apcupsd communication"
+msgstr ""
+
 msgid "Processes"
 msgstr ""
 
@@ -480,6 +492,9 @@ msgstr ""
 msgid "Table"
 msgstr ""
 
+msgid "The APCUPS plugin collects statistics about the APC UPS."
+msgstr ""
+
 msgid "The NUT plugin reads information about Uninterruptible Power Supplies."
 msgstr ""
 
index 6d7056f..860ff95 100644 (file)
@@ -9,6 +9,12 @@ msgstr ""
 "Content-Transfer-Encoding: 8bit\n"
 "Plural-Forms: nplurals=1; plural=0;\n"
 
+msgid "APC UPS"
+msgstr ""
+
+msgid "APCUPS Plugin Configuration"
+msgstr ""
+
 msgid "Action (target)"
 msgstr ""
 
@@ -279,6 +285,9 @@ msgstr ""
 msgid "Monitor filesystem types"
 msgstr ""
 
+msgid "Monitor host"
+msgstr ""
+
 msgid "Monitor hosts"
 msgstr ""
 
@@ -365,6 +374,9 @@ msgstr ""
 msgid "Port"
 msgstr ""
 
+msgid "Port for apcupsd communication"
+msgstr ""
+
 msgid "Processes"
 msgstr ""
 
@@ -488,6 +500,9 @@ msgstr ""
 msgid "Table"
 msgstr ""
 
+msgid "The APCUPS plugin collects statistics about the APC UPS."
+msgstr ""
+
 msgid "The NUT plugin reads information about Uninterruptible Power Supplies."
 msgstr ""
 
index de17a3c..ac9ae50 100644 (file)
@@ -14,6 +14,12 @@ msgstr ""
 "10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
 "X-Generator: Pootle 2.0.6\n"
 
+msgid "APC UPS"
+msgstr ""
+
+msgid "APCUPS Plugin Configuration"
+msgstr ""
+
 msgid "Action (target)"
 msgstr ""
 
@@ -284,6 +290,9 @@ msgstr ""
 msgid "Monitor filesystem types"
 msgstr ""
 
+msgid "Monitor host"
+msgstr ""
+
 msgid "Monitor hosts"
 msgstr ""
 
@@ -370,6 +379,9 @@ msgstr ""
 msgid "Port"
 msgstr ""
 
+msgid "Port for apcupsd communication"
+msgstr ""
+
 msgid "Processes"
 msgstr ""
 
@@ -493,6 +505,9 @@ msgstr ""
 msgid "Table"
 msgstr ""
 
+msgid "The APCUPS plugin collects statistics about the APC UPS."
+msgstr ""
+
 msgid "The NUT plugin reads information about Uninterruptible Power Supplies."
 msgstr ""
 
index bdb7f1a..f5798a2 100644 (file)
@@ -14,6 +14,12 @@ msgstr ""
 "Content-Transfer-Encoding: 8bit\n"
 "X-Generator: Pootle 1.1.0\n"
 
+msgid "APC UPS"
+msgstr ""
+
+msgid "APCUPS Plugin Configuration"
+msgstr ""
+
 msgid "Action (target)"
 msgstr "Action (target)"
 
@@ -294,6 +300,9 @@ msgstr "Kiểm soát đĩa và phân vùng"
 msgid "Monitor filesystem types"
 msgstr "Kiểm soát loại filesystem"
 
+msgid "Monitor host"
+msgstr ""
+
 msgid "Monitor hosts"
 msgstr "Monitor hosts"
 
@@ -380,6 +389,9 @@ msgstr "Cấu hình Ping plugin"
 msgid "Port"
 msgstr ""
 
+msgid "Port for apcupsd communication"
+msgstr ""
+
 msgid "Processes"
 msgstr "Quá trình xử lý"
 
@@ -503,6 +515,9 @@ msgstr "TTl cho gói ping"
 msgid "Table"
 msgstr "Table"
 
+msgid "The APCUPS plugin collects statistics about the APC UPS."
+msgstr ""
+
 msgid "The NUT plugin reads information about Uninterruptible Power Supplies."
 msgstr ""
 
index 46cf59f..20f5a93 100644 (file)
@@ -13,6 +13,12 @@ msgstr ""
 "X-Generator: Poedit 2.0.1\n"
 "Language-Team: \n"
 
+msgid "APC UPS"
+msgstr ""
+
+msgid "APCUPS Plugin Configuration"
+msgstr ""
+
 msgid "Action (target)"
 msgstr "动作(目标)"
 
@@ -289,6 +295,9 @@ msgstr "监测磁盘和分区"
 msgid "Monitor filesystem types"
 msgstr "监测文件系统类型"
 
+msgid "Monitor host"
+msgstr ""
+
 msgid "Monitor hosts"
 msgstr "监测主机"
 
@@ -377,6 +386,9 @@ msgstr "Ping插件配置"
 msgid "Port"
 msgstr "端口"
 
+msgid "Port for apcupsd communication"
+msgstr ""
+
 msgid "Processes"
 msgstr "进程"
 
@@ -500,6 +512,9 @@ msgstr "ping包TTL"
 msgid "Table"
 msgstr "表"
 
+msgid "The APCUPS plugin collects statistics about the APC UPS."
+msgstr ""
+
 msgid "The NUT plugin reads information about Uninterruptible Power Supplies."
 msgstr "NUT插件读取UPS信息。"
 
index cbd6d9d..36e42c1 100644 (file)
@@ -7,6 +7,12 @@ msgstr ""
 "MIME-Version: 1.0\n"
 "Content-Transfer-Encoding: 8bit\n"
 
+msgid "APC UPS"
+msgstr ""
+
+msgid "APCUPS Plugin Configuration"
+msgstr ""
+
 msgid "Action (target)"
 msgstr ""
 
@@ -277,6 +283,9 @@ msgstr ""
 msgid "Monitor filesystem types"
 msgstr ""
 
+msgid "Monitor host"
+msgstr ""
+
 msgid "Monitor hosts"
 msgstr ""
 
@@ -363,6 +372,9 @@ msgstr ""
 msgid "Port"
 msgstr ""
 
+msgid "Port for apcupsd communication"
+msgstr ""
+
 msgid "Processes"
 msgstr ""
 
@@ -486,6 +498,9 @@ msgstr ""
 msgid "Table"
 msgstr ""
 
+msgid "The APCUPS plugin collects statistics about the APC UPS."
+msgstr ""
+
 msgid "The NUT plugin reads information about Uninterruptible Power Supplies."
 msgstr ""
 
index bd1d547..150ef70 100644 (file)
@@ -12,6 +12,9 @@ msgstr ""
 "Plural-Forms: nplurals=1; plural=0;\n"
 "Language: ja\n"
 
+msgid "Actions"
+msgstr "操作"
+
 msgid "Add Interface"
 msgstr "インターフェースの追加"
 
@@ -58,9 +61,6 @@ msgstr "このアップリンクを削除"
 msgid "Device"
 msgstr "デバイス"
 
-msgid "Disabled"
-msgstr "無効"
-
 msgid "Edit"
 msgstr "編集"
 
@@ -124,18 +124,31 @@ msgstr "インターフェース タイムアウト"
 msgid "Interface Wizard"
 msgstr "インターフェース ウィザード"
 
-msgid "Keep travelmate in an active state."
-msgstr "Travelmate をアクティブ状態で維持します。"
+msgid ""
+"Keep travelmate in an active state. Check every n seconds the connection "
+"status, i.e. the uplink availability."
+msgstr ""
+"Travelmate をアクティブ状態で維持します。 n秒ごとにアップリンクの可用性確認と"
+"して、接続状態をチェックします"
 
 msgid "Last rundate"
 msgstr "最終実行日時"
 
-msgid "Mode"
-msgstr "モード"
+msgid "Manual Rescan"
+msgstr "手動再スキャン"
 
-msgid "Name of the uplink interface that triggers travelmate processing."
+msgid "Move down"
+msgstr "下へ"
+
+msgid "Move up"
+msgstr "上へ"
+
+msgid ""
+"Name of the uplink interface that triggers travelmate processing in 'manual' "
+"mode."
 msgstr ""
-"Travelmate の処理のトリガーとなる、アップリンク インターフェースの名前です。"
+"'manual' モード時に Travelmate の処理のトリガーとなる、アップリンク インター"
+"フェースの名前です。"
 
 msgid "Online Status"
 msgstr "オンライン ステータス"
@@ -157,12 +170,14 @@ msgid "Passphrase (%s)"
 msgstr "暗号フレーズ (%s)"
 
 msgid ""
-"Provides an overview of all configured uplink interfaces for travelmate. You "
-"can edit and delete existing interfaces or scan for new uplinks."
+"Provides an overview of all configured uplinks for the travelmate interface "
+"(%s). You can edit, delete or re-order existing uplinks or scan for a new "
+"one. The currently used uplink is emphasized in blue."
 msgstr ""
-"Travelmate における、全ての設定済みアップリンク インターフェースの一覧です。"
-"既存のインターフェースを編集または削除したり、新規アップリンクの追加のために"
-"スキャンを行うことができます。"
+"Travelmate 用インターフェース(%s)に設定済みの全アップリンクの一覧です。既存"
+"のアップリンクの編集や削除、並べ替えを行ったり、スキャンを行って新規アップリ"
+"ンクを追加することができます。現在使用されているアップリンクは、青色で強調さ"
+"れます。"
 
 msgid "Radio selection"
 msgstr "無線の選択"
@@ -170,6 +185,9 @@ msgstr "無線の選択"
 msgid "Repeat scan"
 msgstr "再スキャン"
 
+msgid "Rescan"
+msgstr "再スキャン"
+
 msgid "Restrict travelmate to a dedicated radio, e.g. 'radio0'"
 msgstr "Travelmate が特定の無線に接続するようにします。例: 'radio0'"
 
@@ -179,6 +197,9 @@ msgstr "実行情報"
 msgid "SSID"
 msgstr "SSID"
 
+msgid "SSID (hidden)"
+msgstr "SSID(ステルス)"
+
 msgid "Scan"
 msgstr "スキャン:"
 
@@ -253,9 +274,6 @@ msgstr "不明"
 msgid "Uplink / Trigger interface"
 msgstr "アップリンク/トリガー インターフェース"
 
-msgid "Uplink Interface"
-msgstr "アップリンク インターフェース"
-
 msgid "Uplink SSID"
 msgstr "アップリンク SSID"
 
index 5e09759..d74373d 100644 (file)
@@ -12,6 +12,9 @@ msgstr ""
 "Plural-Forms: nplurals=2; plural=(n > 1);\n"
 "Language: pt_BR\n"
 
+msgid "Actions"
+msgstr ""
+
 msgid "Add Interface"
 msgstr ""
 
@@ -55,9 +58,6 @@ msgstr ""
 msgid "Device"
 msgstr ""
 
-msgid "Disabled"
-msgstr ""
-
 msgid "Edit"
 msgstr ""
 
@@ -117,16 +117,26 @@ msgstr ""
 msgid "Interface Wizard"
 msgstr ""
 
-msgid "Keep travelmate in an active state."
+msgid ""
+"Keep travelmate in an active state. Check every n seconds the connection "
+"status, i.e. the uplink availability."
 msgstr ""
 
 msgid "Last rundate"
 msgstr ""
 
-msgid "Mode"
+msgid "Manual Rescan"
+msgstr ""
+
+msgid "Move down"
+msgstr ""
+
+msgid "Move up"
 msgstr ""
 
-msgid "Name of the uplink interface that triggers travelmate processing."
+msgid ""
+"Name of the uplink interface that triggers travelmate processing in 'manual' "
+"mode."
 msgstr ""
 
 msgid "Online Status"
@@ -149,8 +159,9 @@ msgid "Passphrase (%s)"
 msgstr ""
 
 msgid ""
-"Provides an overview of all configured uplink interfaces for travelmate. You "
-"can edit and delete existing interfaces or scan for new uplinks."
+"Provides an overview of all configured uplinks for the travelmate interface "
+"(%s). You can edit, delete or re-order existing uplinks or scan for a new "
+"one. The currently used uplink is emphasized in blue."
 msgstr ""
 
 msgid "Radio selection"
@@ -159,6 +170,9 @@ msgstr ""
 msgid "Repeat scan"
 msgstr ""
 
+msgid "Rescan"
+msgstr ""
+
 msgid "Restrict travelmate to a dedicated radio, e.g. 'radio0'"
 msgstr ""
 
@@ -168,6 +182,9 @@ msgstr ""
 msgid "SSID"
 msgstr ""
 
+msgid "SSID (hidden)"
+msgstr ""
+
 msgid "Scan"
 msgstr ""
 
@@ -232,9 +249,6 @@ msgstr ""
 msgid "Uplink / Trigger interface"
 msgstr ""
 
-msgid "Uplink Interface"
-msgstr ""
-
 msgid "Uplink SSID"
 msgstr ""
 
index a0e5629..dc0583f 100644 (file)
@@ -1,6 +1,9 @@
 msgid ""
 msgstr "Content-Type: text/plain; charset=UTF-8"
 
+msgid "Actions"
+msgstr ""
+
 msgid "Add Interface"
 msgstr ""
 
@@ -44,9 +47,6 @@ msgstr ""
 msgid "Device"
 msgstr ""
 
-msgid "Disabled"
-msgstr ""
-
 msgid "Edit"
 msgstr ""
 
@@ -106,16 +106,26 @@ msgstr ""
 msgid "Interface Wizard"
 msgstr ""
 
-msgid "Keep travelmate in an active state."
+msgid ""
+"Keep travelmate in an active state. Check every n seconds the connection "
+"status, i.e. the uplink availability."
 msgstr ""
 
 msgid "Last rundate"
 msgstr ""
 
-msgid "Mode"
+msgid "Manual Rescan"
+msgstr ""
+
+msgid "Move down"
+msgstr ""
+
+msgid "Move up"
 msgstr ""
 
-msgid "Name of the uplink interface that triggers travelmate processing."
+msgid ""
+"Name of the uplink interface that triggers travelmate processing in 'manual' "
+"mode."
 msgstr ""
 
 msgid "Online Status"
@@ -138,8 +148,9 @@ msgid "Passphrase (%s)"
 msgstr ""
 
 msgid ""
-"Provides an overview of all configured uplink interfaces for travelmate. You "
-"can edit and delete existing interfaces or scan for new uplinks."
+"Provides an overview of all configured uplinks for the travelmate interface "
+"(%s). You can edit, delete or re-order existing uplinks or scan for a new "
+"one. The currently used uplink is emphasized in blue."
 msgstr ""
 
 msgid "Radio selection"
@@ -148,6 +159,9 @@ msgstr ""
 msgid "Repeat scan"
 msgstr ""
 
+msgid "Rescan"
+msgstr ""
+
 msgid "Restrict travelmate to a dedicated radio, e.g. 'radio0'"
 msgstr ""
 
@@ -157,6 +171,9 @@ msgstr ""
 msgid "SSID"
 msgstr ""
 
+msgid "SSID (hidden)"
+msgstr ""
+
 msgid "Scan"
 msgstr ""
 
@@ -221,9 +238,6 @@ msgstr ""
 msgid "Uplink / Trigger interface"
 msgstr ""
 
-msgid "Uplink Interface"
-msgstr ""
-
 msgid "Uplink SSID"
 msgstr ""
 
index 07aa726..96c73e3 100644 (file)
@@ -19,8 +19,8 @@ msgid ""
 "How often to check internet connection. Default unit is seconds, you can you "
 "use the suffix 'm' for minutes, 'h' for hours or 'd' for days"
 msgstr ""
-"Hur ofta internet-anslutningen ska kollas. Standardenheten är sekunder, du kan använda "
-"tillägget 'm' för minutrar, 't' för timmar eller 'd' för dagar"
+"Hur ofta internet-anslutningen ska kollas. Standardenheten är sekunder, du "
+"kan använda tillägget 'm' för minutrar, 't' för timmar eller 'd' för dagar"
 
 msgid ""
 "In periodic mode, it defines the reboot period. In internet mode, it defines "
index ca4e5aa..50953aa 100644 (file)
@@ -1,5 +1,5 @@
 msgid ""
-msgstr "Content-Type: text/plain; charset=UTF-8"
+msgstr "Content-Type: text/plain; charset=UTF-8\n"
 
 msgid "Activate wifi"
 msgstr "Aktivera wifi"
index b7e3ed5..1aa68e2 100644 (file)
@@ -1,5 +1,5 @@
 msgid ""
-msgstr "Content-Type: text/plain; charset=UTF-8"
+msgstr "Content-Type: text/plain; charset=UTF-8\n"
 
 msgid "Allowed IPs"
 msgstr "Tillåtna IP-adresser"
index 4be917d..b819230 100644 (file)
@@ -481,8 +481,9 @@ function cbi_d_check(deps) {
                                istat = (istat && cbi_d_checkvalue(j, deps[i][j]))
                        }
                }
-               if (istat) {
-                       return !reverse;
+
+               if (istat ^ reverse) {
+                       return true;
                }
        }
        return def;
@@ -648,9 +649,6 @@ function cbi_combobox(id, values, def, man, focus) {
        var dt = obj.getAttribute('cbi_datatype');
        var op = obj.getAttribute('cbi_optional');
 
-       if (dt)
-               cbi_validate_field(sel, op == 'true', dt);
-
        if (!values[obj.value]) {
                if (obj.value == "") {
                        var optdef = document.createElement("option");
@@ -685,6 +683,9 @@ function cbi_combobox(id, values, def, man, focus) {
 
        obj.style.display = "none";
 
+       if (dt)
+               cbi_validate_field(sel, op == 'true', dt);
+
        cbi_bind(sel, "change", function() {
                if (sel.selectedIndex == sel.options.length - 1) {
                        obj.style.display = "inline";
index 701c12a..3385f8f 100644 (file)
@@ -91,8 +91,6 @@ XHR = function()
 
                xhr.open('POST', url, true);
                xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
-               xhr.setRequestHeader('Content-length', code.length);
-               xhr.setRequestHeader('Connection', 'close');
                xhr.send(code);
        }
 
index 99f3ee2..115c54d 100644 (file)
@@ -348,8 +348,10 @@ end
 
 function net.devices()
        local devs = {}
+       local seen = {}
        for k, v in ipairs(nixio.getifaddrs()) do
-               if v.family == "packet" then
+               if v.name and not seen[v.name] then
+                       seen[v.name] = true
                        devs[#devs+1] = v.name
                end
        end
index 9f4efdd..0486ec2 100644 (file)
@@ -2342,6 +2342,9 @@ msgstr ""
 msgid "Password successfully changed!"
 msgstr "La contrasenya s'ha canviat amb èxit!"
 
+msgid "Password2"
+msgstr ""
+
 msgid "Path to CA-Certificate"
 msgstr "Ruta als Certificats CA"
 
index 9efe3b7..c217b0c 100644 (file)
@@ -2365,6 +2365,9 @@ msgstr ""
 msgid "Password successfully changed!"
 msgstr "Heslo bylo úspěšně změněno!"
 
+msgid "Password2"
+msgstr ""
+
 msgid "Path to CA-Certificate"
 msgstr "Cesta k certifikátu CA"
 
index fa26a1d..183e495 100644 (file)
@@ -2427,6 +2427,9 @@ msgstr "Password des inneren, privaten Schlüssels"
 msgid "Password successfully changed!"
 msgstr "Passwort erfolgreich geändert!"
 
+msgid "Password2"
+msgstr ""
+
 msgid "Path to CA-Certificate"
 msgstr "Pfad zum CA-Zertifikat"
 
index e3969af..b385651 100644 (file)
@@ -2372,6 +2372,9 @@ msgstr ""
 msgid "Password successfully changed!"
 msgstr "Ο κωδικός πρόσβασης άλλαξε επιτυχώς!"
 
+msgid "Password2"
+msgstr ""
+
 msgid "Path to CA-Certificate"
 msgstr "Διαδρομή για Πιστοποιητικό CA"
 
index 6537370..0420733 100644 (file)
@@ -2339,6 +2339,9 @@ msgstr ""
 msgid "Password successfully changed!"
 msgstr ""
 
+msgid "Password2"
+msgstr ""
+
 msgid "Path to CA-Certificate"
 msgstr "Path to CA-Certificate"
 
index a347673..626e374 100644 (file)
@@ -2379,6 +2379,9 @@ msgstr ""
 msgid "Password successfully changed!"
 msgstr "¡Contraseña cambiada!"
 
+msgid "Password2"
+msgstr ""
+
 msgid "Path to CA-Certificate"
 msgstr "Ruta al Certificado CA"
 
index 1aab5cb..b0b4b43 100644 (file)
@@ -2392,6 +2392,9 @@ msgstr ""
 msgid "Password successfully changed!"
 msgstr "Mot de passe changé avec succès !"
 
+msgid "Password2"
+msgstr ""
+
 msgid "Path to CA-Certificate"
 msgstr "Chemin de la CA"
 
index d8eae1f..2c2c5d2 100644 (file)
@@ -2306,6 +2306,9 @@ msgstr ""
 msgid "Password successfully changed!"
 msgstr ""
 
+msgid "Password2"
+msgstr ""
+
 msgid "Path to CA-Certificate"
 msgstr ""
 
index dfed45d..8f5aee4 100644 (file)
@@ -2382,6 +2382,9 @@ msgstr ""
 msgid "Password successfully changed!"
 msgstr "A jelszó megváltoztatása sikeres!"
 
+msgid "Password2"
+msgstr ""
+
 msgid "Path to CA-Certificate"
 msgstr "CA tanúsítvány elérési útja"
 
index 04331ab..ea7578e 100644 (file)
@@ -2378,6 +2378,9 @@ msgstr ""
 msgid "Password successfully changed!"
 msgstr "Password cambiata con successo!"
 
+msgid "Password2"
+msgstr ""
+
 msgid "Path to CA-Certificate"
 msgstr "Percorso al certificato CA"
 
index 714b064..7d23abe 100644 (file)
@@ -3,14 +3,14 @@ msgstr ""
 "Project-Id-Version: \n"
 "Report-Msgid-Bugs-To: \n"
 "POT-Creation-Date: 2009-06-10 03:40+0200\n"
-"PO-Revision-Date: 2017-04-03 02:32+0900\n"
+"PO-Revision-Date: 2017-07-28 12:17+0900\n"
 "Last-Translator: INAGAKI Hiroshi <musashino.open@gmail.com>\n"
 "Language: ja\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
 "Plural-Forms: nplurals=1; plural=0;\n"
-"X-Generator: Poedit 2.0\n"
+"X-Generator: Poedit 2.0.3\n"
 "Language-Team: \n"
 
 msgid "%s is untagged in multiple VLANs!"
@@ -44,7 +44,7 @@ msgid "-- match by label --"
 msgstr "-- ラベルを指定 --"
 
 msgid "-- match by uuid --"
-msgstr "-- UUIDを指定 --"
+msgstr "-- UUID を指定 --"
 
 msgid "1 Minute Load:"
 msgstr "過去1分の負荷:"
@@ -157,6 +157,8 @@ msgid ""
 "<br/>Note: you need to manually restart the cron service if the crontab file "
 "was empty before editing."
 msgstr ""
+"<br />注意: 編集前の crontab ファイルが空の場合、手動で cron サービスの再起動"
+"を行う必要があります。"
 
 msgid "A43C + J43 + A43"
 msgstr ""
@@ -283,7 +285,7 @@ msgid "Allocate IP sequentially"
 msgstr ""
 
 msgid "Allow <abbr title=\"Secure Shell\">SSH</abbr> password authentication"
-msgstr "<abbr title=\"Secure Shell\">SSH</abbr> パスワード認証を許可します"
+msgstr "<abbr title=\"Secure Shell\">SSH</abbr> パスワード認証を許可します"
 
 msgid "Allow all except listed"
 msgstr "リスト内の端末からのアクセスを禁止"
@@ -299,10 +301,10 @@ msgstr ""
 "リモートホストがSSH転送されたローカルのポートに接続することを許可します"
 
 msgid "Allow root logins with password"
-msgstr "ã\83\91ã\82¹ã\83¯ã\83¼ã\83\89ã\82\92使ç\94¨ã\81\97ã\81\9froot権é\99\90ã\81§ã\81®ã\83­ã\82°ã\82¤ã\83³ã\82\92許å\8f¯ã\81\99ã\82\8b"
+msgstr "ã\83\91ã\82¹ã\83¯ã\83¼ã\83\89ã\81§ã\81® root ã\83­ã\82°ã\82¤ã\83³ã\82\92許å\8f¯"
 
 msgid "Allow the <em>root</em> user to login with password"
-msgstr "パスワードを使用した<em>root</em>権限でのログインを許可する"
+msgstr "パスワードを使用した <em>root</em> 権限でのログインを許可します。"
 
 msgid ""
 "Allow upstream responses in the 127.0.0.0/8 range, e.g. for RBL services"
@@ -515,8 +517,8 @@ msgid ""
 "defined backup patterns."
 msgstr ""
 "以下は、バックアップの際に含まれるファイルのリストです。このリストは、opkgに"
-"よって認識されている設定ファイル、重要なベースファイル、ユーザーが設定した"
-"規表現に一致したファイルの一覧です。"
+"よって認識されている設定ファイル、重要なベースファイル、ユーザーが設定した"
+"ターンに一致したファイルの一覧です。"
 
 msgid "Bind interface"
 msgstr ""
@@ -858,7 +860,7 @@ msgid "Device is rebooting..."
 msgstr "デバイスを再起動中です..."
 
 msgid "Device unreachable"
-msgstr ""
+msgstr "デバイスに到達できません"
 
 msgid "Diagnostics"
 msgstr "診断機能"
@@ -1217,7 +1219,7 @@ msgid "Force TKIP and CCMP (AES)"
 msgstr "TKIP 及びCCMP (AES) を使用"
 
 msgid "Force link"
-msgstr ""
+msgstr "強制リンク"
 
 msgid "Force use of NAT-T"
 msgstr "NAT-Tの強制使用"
@@ -1467,7 +1469,7 @@ msgid "IPv6 routed prefix"
 msgstr ""
 
 msgid "IPv6 suffix"
-msgstr ""
+msgstr "IPv6 サフィックス"
 
 msgid "IPv6-Address"
 msgstr "IPv6-アドレス"
@@ -2380,6 +2382,9 @@ msgstr ""
 msgid "Password successfully changed!"
 msgstr "パスワードを変更しました"
 
+msgid "Password2"
+msgstr "パスワード2"
+
 msgid "Path to CA-Certificate"
 msgstr "CA証明書のパス"
 
@@ -3672,7 +3677,7 @@ msgid "Waiting for command to complete..."
 msgstr "コマンド実行中です..."
 
 msgid "Waiting for device..."
-msgstr "デバイスの起動をお待ちください..."
+msgstr "デバイスの起動を待っています..."
 
 msgid "Warning"
 msgstr "警告"
index 587b489..770a49c 100644 (file)
@@ -2332,6 +2332,9 @@ msgstr ""
 msgid "Password successfully changed!"
 msgstr ""
 
+msgid "Password2"
+msgstr ""
+
 msgid "Path to CA-Certificate"
 msgstr ""
 
index 9791218..c2f6272 100644 (file)
@@ -2311,6 +2311,9 @@ msgstr ""
 msgid "Password successfully changed!"
 msgstr ""
 
+msgid "Password2"
+msgstr ""
+
 msgid "Path to CA-Certificate"
 msgstr "Path ke CA-Sijil"
 
index 9e3df81..6a6e818 100644 (file)
@@ -2357,6 +2357,9 @@ msgstr ""
 msgid "Password successfully changed!"
 msgstr "Passordet er endret!"
 
+msgid "Password2"
+msgstr ""
+
 msgid "Path to CA-Certificate"
 msgstr "Sti til CA-sertifikat"
 
index 98067c8..e364616 100644 (file)
@@ -2401,6 +2401,9 @@ msgstr ""
 msgid "Password successfully changed!"
 msgstr "Pomyślnie zmieniono hasło!"
 
+msgid "Password2"
+msgstr ""
+
 msgid "Path to CA-Certificate"
 msgstr "Ścieżka do certyfikatu CA"
 
index f2ba575..87c32bf 100644 (file)
@@ -2497,6 +2497,9 @@ msgstr "Senha da Chave Privada interna"
 msgid "Password successfully changed!"
 msgstr "A senha foi alterada com sucesso!"
 
+msgid "Password2"
+msgstr ""
+
 msgid "Path to CA-Certificate"
 msgstr "Caminho para o Certificado da AC"
 
index cb90df5..bea93f5 100644 (file)
@@ -2379,6 +2379,9 @@ msgstr ""
 msgid "Password successfully changed!"
 msgstr "Password alterada com sucesso!"
 
+msgid "Password2"
+msgstr ""
+
 msgid "Path to CA-Certificate"
 msgstr "Directorio do Certificado CA"
 
index d51f4d0..2ee8537 100644 (file)
@@ -2303,6 +2303,9 @@ msgstr ""
 msgid "Password successfully changed!"
 msgstr "Parola schimbata cu succes !"
 
+msgid "Password2"
+msgstr ""
+
 msgid "Path to CA-Certificate"
 msgstr "Calea catre certificatul CA"
 
index d30c643..6515772 100644 (file)
@@ -2387,6 +2387,9 @@ msgstr ""
 msgid "Password successfully changed!"
 msgstr "Пароль успешно изменён!"
 
+msgid "Password2"
+msgstr ""
+
 msgid "Path to CA-Certificate"
 msgstr "Путь к центру сертификации"
 
index f4037ea..ab876ce 100644 (file)
@@ -2278,6 +2278,9 @@ msgstr ""
 msgid "Password successfully changed!"
 msgstr ""
 
+msgid "Password2"
+msgstr ""
+
 msgid "Path to CA-Certificate"
 msgstr ""
 
index 6af6d61..803ac28 100644 (file)
@@ -2284,6 +2284,9 @@ msgstr ""
 msgid "Password successfully changed!"
 msgstr ""
 
+msgid "Password2"
+msgstr ""
+
 msgid "Path to CA-Certificate"
 msgstr ""
 
index d3fc6a7..1aa1816 100644 (file)
@@ -2271,6 +2271,9 @@ msgstr ""
 msgid "Password successfully changed!"
 msgstr ""
 
+msgid "Password2"
+msgstr ""
+
 msgid "Path to CA-Certificate"
 msgstr ""
 
index afabfa2..3c814cd 100644 (file)
@@ -2291,6 +2291,9 @@ msgstr ""
 msgid "Password successfully changed!"
 msgstr ""
 
+msgid "Password2"
+msgstr ""
+
 msgid "Path to CA-Certificate"
 msgstr ""
 
index 7ee0688..83e5501 100644 (file)
@@ -2398,6 +2398,9 @@ msgstr ""
 msgid "Password successfully changed!"
 msgstr "Пароль успішно змінено!"
 
+msgid "Password2"
+msgstr ""
+
 msgid "Path to CA-Certificate"
 msgstr "Шлях до центру сертифікції"
 
index 239e1c2..7bd7868 100644 (file)
@@ -2314,6 +2314,9 @@ msgstr ""
 msgid "Password successfully changed!"
 msgstr ""
 
+msgid "Password2"
+msgstr ""
+
 msgid "Path to CA-Certificate"
 msgstr "Đường dẫn tới CA-Certificate"
 
index 7521324..7b2792e 100644 (file)
@@ -2319,6 +2319,9 @@ msgstr ""
 msgid "Password successfully changed!"
 msgstr "密碼已變更成功!"
 
+msgid "Password2"
+msgstr ""
+
 msgid "Path to CA-Certificate"
 msgstr "CA-證書的路徑"