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 ""
msgid ""
-msgstr "Content-Type: text/plain; charset=UTF-8"
+msgstr "Content-Type: text/plain; charset=UTF-8\n"
msgid "10"
msgstr "10"
"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..."
msgid ""
-msgstr "Content-Type: text/plain; charset=UTF-8"
+msgstr "Content-Type: text/plain; charset=UTF-8\n"
msgid "&"
msgstr "&"
"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"
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 "
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"
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:"
"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."
"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"
msgstr "ルーティング テーブルのチェック"
msgid "Collecting data..."
-msgstr ""
+msgstr "データ収集中です..."
msgid "Configuration"
msgstr "設定"
msgstr "診断機能"
msgid "Disabled"
-msgstr ""
+msgstr "無効"
msgid ""
"Downed interface will be deemed up after this many successful ping tests"
msgstr "有効"
msgid "Error collecting troubleshooting information"
-msgstr ""
+msgstr "トラブルシューティング情報の収集エラー"
msgid "Errors"
msgstr "エラー"
msgstr "インターネット プロトコル"
msgid "Last 50 MWAN systemlog entries. Newest entries sorted at the top :"
-msgstr ""
+msgstr "直近の MWAN システムログ(50行)です。一番上が最新の行です:"
msgid "Last resort"
msgstr "最終手段"
"(例: \"1024:2048\")を、クオーテーション無しで指定することができます。"
msgid "Member"
-msgstr ""
+msgstr "メンバー"
msgid "Member used"
msgstr "使用されるメンバー"
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 "概要"
"ターフェースやメンバー、ルールと同じ名前を使用することはできません。"
msgid "Policy"
-msgstr ""
+msgstr "ポリシー"
msgid "Policy assigned"
msgstr "アサイン済みポリシー"
msgstr "復元..."
msgid "Rule"
-msgstr ""
+msgstr "ルール"
msgid "Rules"
msgstr "ルール"
"いません!プロトコルを指定し直してください!"
msgid "Waiting for MWAN to %s..."
-msgstr ""
+msgstr "MWAN の %s を待っています..."
msgid "Waiting for diagnostic results..."
-msgstr ""
+msgstr "診断結果を待っています..."
msgid "Weight"
msgstr "ウエイト"
msgstr "never"
msgid "restart"
-msgstr ""
+msgstr "再起動"
msgid "start"
-msgstr ""
+msgstr "起動"
msgid "stop"
-msgstr ""
+msgstr "停止"
msgid "unreachable (reject)"
msgstr "unreachable (reject)"
--- /dev/null
+include $(TOPDIR)/rules.mk
+
+LUCI_TITLE:=Netlink based bandwidth accounting
+LUCI_DEPENDS:=+nlbwmon
+
+include ../../luci.mk
+
+# call BuildPackage - OpenWrt buildroot signature
--- /dev/null
+-- 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)
+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
--- /dev/null
+-- 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
--- /dev/null
+<%#
+ 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%>
--- /dev/null
+<%#
+ 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 });
+
+ 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;
+
+ if (i) {
+ d2 = new Date(trafficPeriods[i - 1]);
+ d2.setDate(d2.getDate() - 1);
+ }
+ else {
+ d2 = new Date();
+ }
+
+ var opt = document.createElement('option');
+ opt.setAttribute('data-duration', (d2.getTime() - d1.getTime()) / 1000);
+ opt.value = '%04d-%02d-%02d'.format(d1.getFullYear(), d1.getMonth() + 1, d1.getDate());
+ 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 = [], rxEmpty = true, txEmpty = true;
+
+ 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
+ });
+
+ if (rec.rx_bytes)
+ rxEmpty = false;
+
+ if (rec.tx_bytes)
+ txEmpty = false;
+ }
+
+ if (rxEmpty) {
+ rxData[0].value = 1;
+ rxData[0].color = '#cccccc';
+ rxData[0].label[0] = '<%:no traffic%>';
+ }
+
+ if (txEmpty) {
+ txData[0].value = 1;
+ txData[0].color = '#cccccc';
+ txData[0].label[0] = '<%:no traffic%>';
+ }
+
+ 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;
+ }
+
+ 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]);
+ }
+ }
+
+ 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 tbd = document.createElement('tbody'),
+ row = tbd.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 = tbd.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) : '-';
+
+ table.appendChild(tbd);
+ }
+
+ pie('ipv6-share-pie', [{
+ value: rx4_total + tx4_total,
+ label: ["IPv4: %.2mB"],
+ color: 'hsl(140, 100%, 50%)'
+ }, {
+ value: rx6_total + tx6_total,
+ label: ["IPv6: %.2mB"],
+ color: 'hsl(180, 100%, 50%)'
+ }]);
+
+ pie('ipv6-hosts-pie', [{
+ value: v4_total,
+ label: ["<%:%d IPv4-only hosts%>"],
+ color: 'hsl(140, 100%, 50%)'
+ }, {
+ value: v6_total,
+ label: ["<%:%d IPv6-only hosts%>"],
+ color: 'hsl(180, 100%, 50%)'
+ }, {
+ value: ds_total,
+ label: ["<%:%d dual-stack hosts%>"],
+ color: 'hsl(50, 100%, 50%)'
+ }]);
+
+ 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&group_by=mac&order_by=-rx,-tx"><%:CSV, grouped by MAC%></a></li>
+ <li><a href="<%=url('admin/nlbw/data')%>?type=csv&group_by=ip&order_by=-rx,-tx"><%:CSV, grouped by IP%></a></li>
+ <li><a href="<%=url('admin/nlbw/data')%>?type=csv&group_by=layer7&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(trafficPeriods[0]);
+ }
+ };
+ xhr.send(null);
+ });
+//]]></script>
+
+<%+footer%>
--- /dev/null
+#!/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
"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)"
msgid "Monitor filesystem types"
msgstr "Monitoritza els tipus de sistema de fitxers"
+msgid "Monitor host"
+msgstr ""
+
msgid "Monitor hosts"
msgstr "Monitoritza màquines"
msgid "Port"
msgstr "Port"
+msgid "Port for apcupsd communication"
+msgstr ""
+
msgid "Processes"
msgstr "Processos"
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 ""
"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)"
msgid "Monitor filesystem types"
msgstr "Sledovat typy souborových systémů"
+msgid "Monitor host"
+msgstr ""
+
msgid "Monitor hosts"
msgstr "Sledovat hostitele"
msgid "Port"
msgstr "Port"
+msgid "Port for apcupsd communication"
+msgstr ""
+
msgid "Processes"
msgstr "Procesy"
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 ""
"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)"
msgid "Monitor filesystem types"
msgstr "Datesystemtypen überwachen"
+msgid "Monitor host"
+msgstr ""
+
msgid "Monitor hosts"
msgstr "Hosts überwachen"
msgid "Port"
msgstr "Port"
+msgid "Port for apcupsd communication"
+msgstr ""
+
msgid "Processes"
msgstr "Prozesse"
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"
"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 ""
msgid "Monitor filesystem types"
msgstr ""
+msgid "Monitor host"
+msgstr ""
+
msgid "Monitor hosts"
msgstr ""
msgid "Port"
msgstr ""
+msgid "Port for apcupsd communication"
+msgstr ""
+
msgid "Processes"
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 ""
"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)"
msgid "Monitor filesystem types"
msgstr "Monitor filesystem types"
+msgid "Monitor host"
+msgstr ""
+
msgid "Monitor hosts"
msgstr "Monitor hosts"
msgid "Port"
msgstr ""
+msgid "Port for apcupsd communication"
+msgstr ""
+
msgid "Processes"
msgstr "Processes"
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 ""
"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)"
msgid "Monitor filesystem types"
msgstr "Monitorizar tipos de sistema de archivos"
+msgid "Monitor host"
+msgstr ""
+
msgid "Monitor hosts"
msgstr "Monitorizar máquinas"
msgid "Port"
msgstr "Puerto"
+msgid "Port for apcupsd communication"
+msgstr ""
+
msgid "Processes"
msgstr "Procesos"
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 "
"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)"
msgid "Monitor filesystem types"
msgstr "types de systèmes de fichier à surveiller"
+msgid "Monitor host"
+msgstr ""
+
msgid "Monitor hosts"
msgstr "Hôtes à surveiller"
msgid "Port"
msgstr ""
+msgid "Port for apcupsd communication"
+msgstr ""
+
msgid "Processes"
msgstr "Processus"
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 ""
"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 ""
msgid "Monitor filesystem types"
msgstr ""
+msgid "Monitor host"
+msgstr ""
+
msgid "Monitor hosts"
msgstr ""
msgid "Port"
msgstr ""
+msgid "Port for apcupsd communication"
+msgstr ""
+
msgid "Processes"
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 ""
"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)"
msgid "Monitor filesystem types"
msgstr "Fájlrendszer típusok figyelése"
+msgid "Monitor host"
+msgstr ""
+
msgid "Monitor hosts"
msgstr "Gépek figyelése"
msgid "Port"
msgstr "Port"
+msgid "Port for apcupsd communication"
+msgstr ""
+
msgid "Processes"
msgstr "Folyamatok"
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."
"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)"
msgid "Monitor filesystem types"
msgstr ""
+msgid "Monitor host"
+msgstr ""
+
msgid "Monitor hosts"
msgstr ""
msgid "Port"
msgstr ""
+msgid "Port for apcupsd communication"
+msgstr ""
+
msgid "Processes"
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 ""
"X-Generator: Poedit 1.8.11\n"
"Language-Team: \n"
+msgid "APC UPS"
+msgstr ""
+
+msgid "APCUPS Plugin Configuration"
+msgstr ""
+
msgid "Action (target)"
msgstr "アクション(対象)"
msgid "Monitor filesystem types"
msgstr "ファイルシステム タイプをモニターする"
+msgid "Monitor host"
+msgstr ""
+
msgid "Monitor hosts"
msgstr "ホストをモニターする"
msgid "Port"
msgstr "ポート"
+msgid "Port for apcupsd communication"
+msgstr ""
+
msgid "Processes"
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 "NUT プラグインは、無停電電源装置についての情報を読み取ります。"
"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 ""
msgid "Monitor filesystem types"
msgstr ""
+msgid "Monitor host"
+msgstr ""
+
msgid "Monitor hosts"
msgstr ""
msgid "Port"
msgstr ""
+msgid "Port for apcupsd communication"
+msgstr ""
+
msgid "Processes"
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 ""
"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)"
msgid "Monitor filesystem types"
msgstr "Overvåk filsystem typer"
+msgid "Monitor host"
+msgstr ""
+
msgid "Monitor hosts"
msgstr "Overvåk verter"
msgid "Port"
msgstr ""
+msgid "Port for apcupsd communication"
+msgstr ""
+
msgid "Processes"
msgstr "Prosesser"
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 ""
"|| 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)"
msgid "Monitor filesystem types"
msgstr "Monitoruj system plików"
+msgid "Monitor host"
+msgstr ""
+
msgid "Monitor hosts"
msgstr "Monitoruj hosty"
msgid "Port"
msgstr "Port"
+msgid "Port for apcupsd communication"
+msgstr ""
+
msgid "Processes"
msgstr "Procesy"
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"
"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)"
msgid "Monitor filesystem types"
msgstr "Monitorar tipos de sistemas de arquivos"
+msgid "Monitor host"
+msgstr ""
+
msgid "Monitor hosts"
msgstr "Monitorar os equipamentos"
msgid "Port"
msgstr "Porta"
+msgid "Port for apcupsd communication"
+msgstr ""
+
msgid "Processes"
msgstr "Processos"
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."
"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)"
msgid "Monitor filesystem types"
msgstr "Monitorar tipos de sistemas de arquivos"
+msgid "Monitor host"
+msgstr ""
+
msgid "Monitor hosts"
msgstr "Monitorar os hosts"
msgid "Port"
msgstr "Porta"
+msgid "Port for apcupsd communication"
+msgstr ""
+
msgid "Processes"
msgstr "Processos"
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 ""
"20)) ? 1 : 2);;\n"
"X-Generator: Pootle 2.0.4\n"
+msgid "APC UPS"
+msgstr ""
+
+msgid "APCUPS Plugin Configuration"
+msgstr ""
+
msgid "Action (target)"
msgstr ""
msgid "Monitor filesystem types"
msgstr ""
+msgid "Monitor host"
+msgstr ""
+
msgid "Monitor hosts"
msgstr ""
msgid "Port"
msgstr ""
+msgid "Port for apcupsd communication"
+msgstr ""
+
msgid "Processes"
msgstr "Procese"
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 ""
"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 "Действие (цель)"
msgid "Monitor filesystem types"
msgstr "Собирать статистику с файловых систем"
+msgid "Monitor host"
+msgstr ""
+
msgid "Monitor hosts"
msgstr "Собирать статистику с хостов"
msgid "Port"
msgstr "Порт"
+msgid "Port for apcupsd communication"
+msgstr ""
+
msgid "Processes"
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 ""
"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 ""
msgid "Monitor filesystem types"
msgstr ""
+msgid "Monitor host"
+msgstr ""
+
msgid "Monitor hosts"
msgstr ""
msgid "Port"
msgstr ""
+msgid "Port for apcupsd communication"
+msgstr ""
+
msgid "Processes"
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 ""
"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 ""
msgid "Monitor filesystem types"
msgstr "Övervaka filsystemtyper"
+msgid "Monitor host"
+msgstr ""
+
msgid "Monitor hosts"
msgstr "Övervaka värdar"
msgid "Port"
msgstr "Port"
+msgid "Port for apcupsd communication"
+msgstr ""
+
msgid "Processes"
msgstr "Processer"
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 ""
msgid ""
msgstr "Content-Type: text/plain; charset=UTF-8"
+msgid "APC UPS"
+msgstr ""
+
+msgid "APCUPS Plugin Configuration"
+msgstr ""
+
msgid "Action (target)"
msgstr ""
msgid "Monitor filesystem types"
msgstr ""
+msgid "Monitor host"
+msgstr ""
+
msgid "Monitor hosts"
msgstr ""
msgid "Port"
msgstr ""
+msgid "Port for apcupsd communication"
+msgstr ""
+
msgid "Processes"
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 ""
"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 ""
msgid "Monitor filesystem types"
msgstr ""
+msgid "Monitor host"
+msgstr ""
+
msgid "Monitor hosts"
msgstr ""
msgid "Port"
msgstr ""
+msgid "Port for apcupsd communication"
+msgstr ""
+
msgid "Processes"
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 ""
"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 ""
msgid "Monitor filesystem types"
msgstr ""
+msgid "Monitor host"
+msgstr ""
+
msgid "Monitor hosts"
msgstr ""
msgid "Port"
msgstr ""
+msgid "Port for apcupsd communication"
+msgstr ""
+
msgid "Processes"
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 ""
"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)"
msgid "Monitor filesystem types"
msgstr "Kiểm soát loại filesystem"
+msgid "Monitor host"
+msgstr ""
+
msgid "Monitor hosts"
msgstr "Monitor hosts"
msgid "Port"
msgstr ""
+msgid "Port for apcupsd communication"
+msgstr ""
+
msgid "Processes"
msgstr "Quá trình xử lý"
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 ""
"X-Generator: Poedit 2.0.1\n"
"Language-Team: \n"
+msgid "APC UPS"
+msgstr ""
+
+msgid "APCUPS Plugin Configuration"
+msgstr ""
+
msgid "Action (target)"
msgstr "动作(目标)"
msgid "Monitor filesystem types"
msgstr "监测文件系统类型"
+msgid "Monitor host"
+msgstr ""
+
msgid "Monitor hosts"
msgstr "监测主机"
msgid "Port"
msgstr "端口"
+msgid "Port for apcupsd communication"
+msgstr ""
+
msgid "Processes"
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 "NUT插件读取UPS信息。"
"MIME-Version: 1.0\n"
"Content-Transfer-Encoding: 8bit\n"
+msgid "APC UPS"
+msgstr ""
+
+msgid "APCUPS Plugin Configuration"
+msgstr ""
+
msgid "Action (target)"
msgstr ""
msgid "Monitor filesystem types"
msgstr ""
+msgid "Monitor host"
+msgstr ""
+
msgid "Monitor hosts"
msgstr ""
msgid "Port"
msgstr ""
+msgid "Port for apcupsd communication"
+msgstr ""
+
msgid "Processes"
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 ""
msgid "Mode"
msgstr "モード"
+msgid "Move down"
+msgstr ""
+
+msgid "Move up"
+msgstr ""
+
msgid "Name of the uplink interface that triggers travelmate processing."
msgstr ""
"Travelmate の処理のトリガーとなる、アップリンク インターフェースの名前です。"
msgid "SSID"
msgstr "SSID"
+msgid "SSID (hidden)"
+msgstr ""
+
msgid "Scan"
msgstr "スキャン:"
msgid "Mode"
msgstr ""
+msgid "Move down"
+msgstr ""
+
+msgid "Move up"
+msgstr ""
+
msgid "Name of the uplink interface that triggers travelmate processing."
msgstr ""
msgid "SSID"
msgstr ""
+msgid "SSID (hidden)"
+msgstr ""
+
msgid "Scan"
msgstr ""
msgid "Mode"
msgstr ""
+msgid "Move down"
+msgstr ""
+
+msgid "Move up"
+msgstr ""
+
msgid "Name of the uplink interface that triggers travelmate processing."
msgstr ""
msgid "SSID"
msgstr ""
+msgid "SSID (hidden)"
+msgstr ""
+
msgid "Scan"
msgstr ""
"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 "
msgid ""
-msgstr "Content-Type: text/plain; charset=UTF-8"
+msgstr "Content-Type: text/plain; charset=UTF-8\n"
msgid "Activate wifi"
msgstr "Aktivera wifi"
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"
istat = (istat && cbi_d_checkvalue(j, deps[i][j]))
}
}
- if (istat) {
- return !reverse;
+
+ if (istat ^ reverse) {
+ return true;
}
}
return def;
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");
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";
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
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"
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"
msgid "Password successfully changed!"
msgstr "Passwort erfolgreich geändert!"
+msgid "Password2"
+msgstr ""
+
msgid "Path to CA-Certificate"
msgstr "Pfad zum CA-Zertifikat"
msgid "Password successfully changed!"
msgstr "Ο κωδικός πρόσβασης άλλαξε επιτυχώς!"
+msgid "Password2"
+msgstr ""
+
msgid "Path to CA-Certificate"
msgstr "Διαδρομή για Πιστοποιητικό CA"
msgid "Password successfully changed!"
msgstr ""
+msgid "Password2"
+msgstr ""
+
msgid "Path to CA-Certificate"
msgstr "Path to CA-Certificate"
msgid "Password successfully changed!"
msgstr "¡Contraseña cambiada!"
+msgid "Password2"
+msgstr ""
+
msgid "Path to CA-Certificate"
msgstr "Ruta al Certificado CA"
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"
msgid "Password successfully changed!"
msgstr ""
+msgid "Password2"
+msgstr ""
+
msgid "Path to CA-Certificate"
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"
msgid "Password successfully changed!"
msgstr "Password cambiata con successo!"
+msgid "Password2"
+msgstr ""
+
msgid "Path to CA-Certificate"
msgstr "Percorso al certificato CA"
"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!"
msgstr "-- ラベルを指定 --"
msgid "-- match by uuid --"
-msgstr "-- UUIDを指定 --"
+msgstr "-- UUID を指定 --"
msgid "1 Minute Load:"
msgstr "過去1分の負荷:"
"<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 ""
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 "リスト内の端末からのアクセスを禁止"
"リモートホストが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"
"defined backup patterns."
msgstr ""
"以下は、バックアップの際に含まれるファイルのリストです。このリストは、opkgに"
-"よって認識されている設定ファイル、重要なベースファイル、ユーザーが設定した正"
-"規表現に一致したファイルの一覧です。"
+"よって認識されている設定ファイル、重要なベースファイル、ユーザーが設定したパ"
+"ターンに一致したファイルの一覧です。"
msgid "Bind interface"
msgstr ""
msgstr "デバイスを再起動中です..."
msgid "Device unreachable"
-msgstr ""
+msgstr "デバイスに到達できません"
msgid "Diagnostics"
msgstr "診断機能"
msgstr "TKIP 及びCCMP (AES) を使用"
msgid "Force link"
-msgstr ""
+msgstr "強制リンク"
msgid "Force use of NAT-T"
msgstr "NAT-Tの強制使用"
msgstr ""
msgid "IPv6 suffix"
-msgstr ""
+msgstr "IPv6 サフィックス"
msgid "IPv6-Address"
msgstr "IPv6-アドレス"
msgid "Password successfully changed!"
msgstr "パスワードを変更しました"
+msgid "Password2"
+msgstr "パスワード2"
+
msgid "Path to CA-Certificate"
msgstr "CA証明書のパス"
msgstr "コマンド実行中です..."
msgid "Waiting for device..."
-msgstr "デバイスの起動をお待ちください..."
+msgstr "デバイスの起動を待っています..."
msgid "Warning"
msgstr "警告"
msgid "Password successfully changed!"
msgstr ""
+msgid "Password2"
+msgstr ""
+
msgid "Path to CA-Certificate"
msgstr ""
msgid "Password successfully changed!"
msgstr ""
+msgid "Password2"
+msgstr ""
+
msgid "Path to CA-Certificate"
msgstr "Path ke CA-Sijil"
msgid "Password successfully changed!"
msgstr "Passordet er endret!"
+msgid "Password2"
+msgstr ""
+
msgid "Path to CA-Certificate"
msgstr "Sti til CA-sertifikat"
msgid "Password successfully changed!"
msgstr "Pomyślnie zmieniono hasło!"
+msgid "Password2"
+msgstr ""
+
msgid "Path to CA-Certificate"
msgstr "Ścieżka do certyfikatu CA"
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"
msgid "Password successfully changed!"
msgstr "Password alterada com sucesso!"
+msgid "Password2"
+msgstr ""
+
msgid "Path to CA-Certificate"
msgstr "Directorio do Certificado CA"
msgid "Password successfully changed!"
msgstr "Parola schimbata cu succes !"
+msgid "Password2"
+msgstr ""
+
msgid "Path to CA-Certificate"
msgstr "Calea catre certificatul CA"
msgid "Password successfully changed!"
msgstr "Пароль успешно изменён!"
+msgid "Password2"
+msgstr ""
+
msgid "Path to CA-Certificate"
msgstr "Путь к центру сертификации"
msgid "Password successfully changed!"
msgstr ""
+msgid "Password2"
+msgstr ""
+
msgid "Path to CA-Certificate"
msgstr ""
msgid "Password successfully changed!"
msgstr ""
+msgid "Password2"
+msgstr ""
+
msgid "Path to CA-Certificate"
msgstr ""
msgid "Password successfully changed!"
msgstr ""
+msgid "Password2"
+msgstr ""
+
msgid "Path to CA-Certificate"
msgstr ""
msgid "Password successfully changed!"
msgstr ""
+msgid "Password2"
+msgstr ""
+
msgid "Path to CA-Certificate"
msgstr ""
msgid "Password successfully changed!"
msgstr "Пароль успішно змінено!"
+msgid "Password2"
+msgstr ""
+
msgid "Path to CA-Certificate"
msgstr "Шлях до центру сертифікції"
msgid "Password successfully changed!"
msgstr ""
+msgid "Password2"
+msgstr ""
+
msgid "Path to CA-Certificate"
msgstr "Đường dẫn tới CA-Certificate"
msgid "Password successfully changed!"
msgstr "密碼已變更成功!"
+msgid "Password2"
+msgstr ""
+
msgid "Path to CA-Certificate"
msgstr "CA-證書的路徑"