BB: carry luci2 from feeds to local package dir

This commit is contained in:
Sukru Senli 2015-04-14 10:32:30 +02:00 committed by Martin Schröder
parent 1e8502a267
commit 620068ecb0
128 changed files with 33984 additions and 0 deletions

View file

@ -0,0 +1,36 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=luci2-p910nd
PKG_RELEASE:=1
include $(INCLUDE_DIR)/package.mk
define Package/luci2-p910nd
SECTION:=luci2
CATEGORY:=LuCI2
TITLE:=LuCI2 p910nd module
DEPENDS:=luci2 p910nd
endef
define Package/luci2-p910nd/description
LuCI2 module for p910nd printer daemon management
endef
define Build/Compile
endef
define Package/luci2-p910nd/install
$(INSTALL_DIR) $(1)/usr/share/rpcd/menu.d
$(INSTALL_DATA) ./files/usr/share/rpcd/menu.d/services.p910nd.json $(1)/usr/share/rpcd/menu.d/
$(INSTALL_DIR) $(1)/usr/share/rpcd/acl.d
$(INSTALL_DATA) ./files/usr/share/rpcd/acl.d/services.p910nd.json $(1)/usr/share/rpcd/acl.d/
$(INSTALL_DIR) $(1)/www/luci2/template
$(INSTALL_DATA) ./files/www/luci2/template/services.p910nd.htm $(1)/www/luci2/template
$(INSTALL_DIR) $(1)/www/luci2/view
$(INSTALL_DATA) ./files/www/luci2/view/services.p910nd.js $(1)/www/luci2/view
endef
$(eval $(call BuildPackage,luci2-p910nd))

View file

@ -0,0 +1,15 @@
{
"p910nd": {
"description": "P910ND management",
"read": {
"uci": [
"p910nd"
]
},
"write": {
"uci": [
"p910nd"
]
}
}
}

View file

@ -0,0 +1,11 @@
{
"services": {
"title": "Services",
"index": 40
},
"services/p910nd": {
"title": "p910nd",
"acls": [ "p910nd" ],
"view": "services/p910nd"
}
}

View file

@ -0,0 +1 @@
<div id="map"></div>

View file

@ -0,0 +1,61 @@
L.ui.view.extend({
PortValue: L.cbi.InputValue.extend({
ucivalue: function(sid)
{
var v = this.callSuper('ucivalue', sid);
if (typeof v == 'undefined')
return '';
return 9100 + parseInt(v);
},
formvalue: function(sid)
{
var v = $('#' + this.id(sid)).val();
return parseInt(v) - 9100;
}
}),
execute: function() {
var self = this;
var m = new L.cbi.Map('p910nd', {
caption: L.tr('p910nd printer daemon')
});
var s = m.section(L.cbi.TypedSection, 'p910nd', {
caption: L.tr('Printers'),
addremove: true,
add_caption: L.tr('Add shared printer …'),
});
s.option(L.cbi.CheckboxValue, 'enabled', {
caption: L.tr('Enabled'),
initial: 0,
enabled: '1',
disabled: '0'
});
s.option(self.PortValue, 'port', {
caption: L.tr('Port'),
description: L.tr('Specifies the listening port'),
datatype: 'range(9100, 65535)'
});
s.option(L.cbi.InputValue, 'device', {
caption: L.tr('Device'),
description: L.tr('Printer device path in the filesystem')
});
s.option(L.cbi.CheckboxValue, 'bidirectional', {
caption: L.tr('Bidirectional'),
description: L.tr('Bidirectional copying'),
initial: 1,
enabled: '1',
disabled: '0'
});
return m.insertInto('#map');
}
});

62
luci2/luci2/Makefile Normal file
View file

@ -0,0 +1,62 @@
#
# Copyright (C) 2013 Jo-Philipp Wich <jow@openwrt.org>
#
# Licensed under the Apache License, Version 2.0.
#
include $(TOPDIR)/rules.mk
PKG_NAME:=luci2
PKG_VERSION:=$(shell git --git-dir=$(CURDIR)/../.git log -1 --pretty="%ci %h" | awk '{ print $$1 "-" $$4 }')
PKG_MAINTAINER:=Jo-Philipp Wich <jow@openwrt.org>
PKG_LICENSE:=Apache-2.0
PKG_LICENSE_FILES:=
PKG_BUILD_PARALLEL:=1
include $(INCLUDE_DIR)/package.mk
include $(INCLUDE_DIR)/cmake.mk
define Build/Prepare
$(INSTALL_DIR) $(PKG_BUILD_DIR)
$(CP) ./src/* $(PKG_BUILD_DIR)/
endef
define Package/luci2
SECTION:=luci2
CATEGORY:=LuCI2
TITLE:=LuCI2 UI
DEPENDS:=+rpcd +rpcd-mod-iwinfo +uhttpd +uhttpd-mod-ubus +libubox +libubus
endef
define Package/luci2/description
Provides the LuCI2 web interface with standard functionality.
endef
define Package/luci2/install
$(INSTALL_DIR) $(1)/www
$(CP) ./htdocs/* $(1)/www/
$(INSTALL_DIR) $(1)/usr/share/rpcd
$(CP) ./share/* $(1)/usr/share/rpcd/
$(INSTALL_DIR) $(1)/usr/lib/rpcd
$(INSTALL_BIN) $(PKG_BUILD_DIR)/rpcd/luci2.so $(1)/usr/lib/rpcd/
$(INSTALL_BIN) $(PKG_BUILD_DIR)/rpcd/bwmon.so $(1)/usr/lib/rpcd/
$(INSTALL_DIR) $(1)/usr/libexec $(1)/www/cgi-bin
$(INSTALL_BIN) $(PKG_BUILD_DIR)/io/luci2-io $(1)/usr/libexec/
$(LN) /usr/libexec/luci2-io $(1)/www/cgi-bin/luci-upload
$(LN) /usr/libexec/luci2-io $(1)/www/cgi-bin/luci-backup
endef
define Package/luci2/postinst
#!/bin/sh
if [ "$$(uci -q get uhttpd.main.ubus_prefix)" != "/ubus" ]; then
uci set uhttpd.main.ubus_prefix="/ubus"
uci commit uhttpd
fi
exit 0
endef
$(eval $(call BuildPackage,luci2))

View file

@ -0,0 +1,59 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>LuCI2</title>
<link rel="stylesheet" href="/luci2/css/bootstrap.css">
<link rel="stylesheet" href="/luci2/css/luci2.css">
<!--[if lt IE 9]>
<script src="/luci2/html5.js"></script>
<script src="/luci2/respond.min.js"></script>
<![endif]-->
<script type="text/javascript" src="/luci2/jquery-1.9.1.js"></script>
<script type="text/javascript" src="/luci2/jquery.peity.js"></script>
<script type="text/javascript" src="/luci2/bootstrap.js"></script>
<script type="text/javascript" src="/luci2/luci2.js"></script>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<script type="text/javascript">
$(function() {
var L = new LuCI2();
L.ui.login().then(function() {
L.ui.load();
});
});
</script>
</head>
<body>
<div class="navbar navbar-inverse navbar-fixed-top" role="banner">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#" id="hostname">&nbsp;</a>
</div>
<div class="collapse navbar-collapse" id="mainmenu"></div>
</div>
</div>
<div id="viewmenu"></div>
<div id="maincontent" class="container">
</div>
<!--
<footer>
<a href="http://luci.subsignal.org/">Powered by LuCI Trunk (git-8b6d8db)</a>
OpenWrt Barrier Breaker r37529
</footer>
-->
</body>
</html>

1488
luci2/luci2/htdocs/luci2/bootstrap.js vendored Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

5061
luci2/luci2/htdocs/luci2/css/bootstrap.css vendored Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,268 @@
body {
padding-top: 55px;
}
.nowrap {
white-space: nowrap;
}
.fade.in {
background-color: rgba(0, 0, 0, 0.5);
}
.modal.fade.wide .modal-dialog {
width: 98%;
max-width: 900px;
}
.progress {
display: inline-block;
position: relative;
max-width: 250px;
width: 100%;
margin-bottom: 0;
}
.progress-bar {
z-index: 1;
position: absolute;
}
.progress small {
position: absolute;
top: 0;
z-index: 2;
color: black;
text-align: center;
width: 100%;
}
.nav.nav-tabs {
margin-bottom: 15px;
}
.luci2-modal-loader {
background: #FFFFFF url(/luci2/icons/loading.gif) no-repeat 10px center;
text-align: center;
}
.luci2-section-header {
position: relative;
}
.luci2-section-header .badge {
position: absolute;
left: -11px;
top: -7px;
background-color: #D9534F;
}
.nav-tabs li a .badge {
background-color: #D9534F;
}
.nav-tabs li.active a .badge {
display: none;
}
.list-group-item .luci2-section-header > .btn-group {
position: absolute;
top: 2px;
right: 2px;
z-index: 10;
box-shadow: -5px 0 5px 10px #FFFFFF;
display: none;
}
.list-group-item:hover .luci2-section-header > .btn-group {
display: inline-block;
}
.luci2-section-teaser {
font-size: 90%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
cursor: pointer;
margin-bottom: 0 !important;
}
.panel > .list-group > .list-group-item:first-child {
border-top: none;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
}
.panel > .panel-body + .list-group > .list-group-item:first-child {
border-top: 1px solid #DDDDDD;
border-top-left-radius: 0;
border-top-right-radius: 0;
}
.row.condensed {
margin-left: 0;
margin-right: 0;
}
.row.condensed > div {
padding: 0 1px 0 0;
}
.row.condensed > div:last-child {
padding-right: 0;
}
table.table td .btn-group {
white-space: nowrap;
}
table.table td .btn-group > .btn {
float: none;
}
.luci2-form-error .control-label {
color: #B94A48;
}
pre.uci-changes {
margin: 3px 0;
}
pre.uci-changes ins {
text-decoration: none;
color: #008000;
}
pre.uci-changes del {
text-decoration: none;
color: #800000;
}
.ifacebox {
border: 1px solid #DDDDDD;
border-radius: 3px;
background-color: #FFFFFF;
text-align: center;
white-space: nowrap;
}
.ifacebox-head {
border-bottom: 1px solid #DDDDDD;
padding: 0 3px;
}
.traffic-chart {
position: relative;
}
.traffic-chart label {
position: absolute;
left: 0;
top: 0;
background-color: rgba(255, 255, 255, 0.5);
font-size: 80%;
font-weight: normal;
}
.badge input[type=text] {
border: none;
border-radius: 3px;
padding: 1px 3px;
}
.luci2-grid {
margin-bottom: 20px;
}
.luci2-grid > .row {
border-top: 1px solid #e5e5e5;
margin-left: 0;
margin-right: 0;
}
.luci2-grid > .row:last-child {
border-bottom: 1px solid #e5e5e5;
}
.luci2-grid > .row > .cell {
padding-top: 5px;
padding-bottom: 5px;
display: inline-block !important;
vertical-align: middle;
float: none;
}
.luci2-grid > .row > .cell.nowrap {
overflow: hidden;
text-overflow: ellipsis;
}
.luci2-grid > .row > .cell.caption {
font-weight: bold;
}
.luci2-grid-condensed {
margin-top: -1px;
margin-bottom: -1px;
}
.luci2-grid-condensed > .row > .cell {
padding: 5px 3px;
vertical-align: top;
}
.luci2-grid-hover > .row:hover {
background-color: #f5f5f5;
}
.luci2-grid > .row > .cell > .btn-group {
white-space: nowrap;
}
.luci2-grid > .row > .cell > .btn-group > .btn {
float: none;
}
@media (max-width: 767px) {
body {
font-size: 13px;
}
.badge {
padding: 2px 4px;
}
.container {
padding-left: 5px;
padding-right: 5px;
}
.luci2-grid > .row > .cell.hidden-xs,
.luci2-grid > .row > .cell.hidden-sm.hidden-xs,
.luci2-grid > .row > .cell.hidden-md.hidden-xs,
.luci2-grid > .row > .cell.hidden-lg.hidden-xs { display: none !important; }
.luci2-grid > .row > .cell { padding: 5px; }
.luci2-grid-condensed > .row > .cell {
vertical-align: middle;
padding: 5px 10px;
}
}
@media (min-width: 768px) and (max-width: 991px) {
.luci2-grid > .row > .cell.hidden-xs.hidden-sm,
.luci2-grid > .row > .cell.hidden-sm,
.luci2-grid > .row > .cell.hidden-md.hidden-sm,
.luci2-grid > .row > .cell.hidden-lg.hidden-sm { display: none !important; }
.luci2-grid > .row > .cell { padding: 5px; }
.luci2-grid-condensed > .row > .cell > .btn-group > .btn {
padding: 6px;
}
}
@media (min-width: 992px) and (max-width: 1199px) {
.luci2-grid > .row > .cell.hidden-xs.hidden-md,
.luci2-grid > .row > .cell.hidden-sm.hidden-md,
.luci2-grid > .row > .cell.hidden-md,
.luci2-grid > .row > .cell.hidden-lg.hidden-md { display: none !important; }
}
@media (min-width: 1200px) {
.luci2-grid > .row > .cell.hidden-xs.hidden-lg,
.luci2-grid > .row > .cell.hidden-sm.hidden-lg,
.luci2-grid > .row > .cell.hidden-md.hidden-lg,
.luci2-grid > .row > .cell.hidden-lg { display: none !important; }
}

View file

@ -0,0 +1,50 @@
Class.extend({
getZoneColor: function(zone)
{
if ($.isPlainObject(zone))
zone = zone.name;
if (zone == 'lan')
return '#90f090';
else if (zone == 'wan')
return '#f09090';
for (var i = 0, hash = 0;
i < zone.length;
hash = zone.charCodeAt(i++) + ((hash << 5) - hash));
for (var i = 0, color = '#';
i < 3;
color += ('00' + ((hash >> i++ * 8) & 0xFF).tostring(16)).slice(-2));
return color;
},
findZoneByNetwork: function(network)
{
var self = this;
var zone = undefined;
return L.uci.sections('firewall', 'zone', function(z) {
if (!z.name || !z.network)
return;
if (!$.isArray(z.network))
z.network = z.network.split(/\s+/);
for (var i = 0; i < z.network.length; i++)
{
if (z.network[i] == network)
{
zone = z;
break;
}
}
}).then(function() {
if (zone)
zone.color = self.getZoneColor(zone);
return zone;
});
}
});

View file

@ -0,0 +1,8 @@
/*
HTML5 Shiv v3.7.0 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed
*/
(function(l,f){function m(){var a=e.elements;return"string"==typeof a?a.split(" "):a}function i(a){var b=n[a[o]];b||(b={},h++,a[o]=h,n[h]=b);return b}function p(a,b,c){b||(b=f);if(g)return b.createElement(a);c||(c=i(b));b=c.cache[a]?c.cache[a].cloneNode():r.test(a)?(c.cache[a]=c.createElem(a)).cloneNode():c.createElem(a);return b.canHaveChildren&&!s.test(a)?c.frag.appendChild(b):b}function t(a,b){if(!b.cache)b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag();
a.createElement=function(c){return!e.shivMethods?b.createElem(c):p(c,a,b)};a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+m().join().replace(/[\w\-]+/g,function(a){b.createElem(a);b.frag.createElement(a);return'c("'+a+'")'})+");return n}")(e,b.frag)}function q(a){a||(a=f);var b=i(a);if(e.shivCSS&&!j&&!b.hasCSS){var c,d=a;c=d.createElement("p");d=d.getElementsByTagName("head")[0]||d.documentElement;c.innerHTML="x<style>article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}</style>";
c=d.insertBefore(c.lastChild,d.firstChild);b.hasCSS=!!c}g||t(a,b);return a}var k=l.html5||{},s=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,r=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,j,o="_html5shiv",h=0,n={},g;(function(){try{var a=f.createElement("a");a.innerHTML="<xyz></xyz>";j="hidden"in a;var b;if(!(b=1==a.childNodes.length)){f.createElement("a");var c=f.createDocumentFragment();b="undefined"==typeof c.cloneNode||
"undefined"==typeof c.createDocumentFragment||"undefined"==typeof c.createElement}g=b}catch(d){g=j=!0}})();var e={elements:k.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output progress section summary template time video",version:"3.7.0",shivCSS:!1!==k.shivCSS,supportsUnknownElements:g,shivMethods:!1!==k.shivMethods,type:"default",shivDocument:q,createElement:p,createDocumentFragment:function(a,b){a||(a=f);
if(g)return a.createDocumentFragment();for(var b=b||i(a),c=b.frag.cloneNode(),d=0,e=m(),h=e.length;d<h;d++)c.createElement(e[d]);return c}};l.html5=e;q(f)})(this,document);

Binary file not shown.

After

Width:  |  Height:  |  Size: 838 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 517 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 378 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 268 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 280 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 379 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 371 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 273 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 698 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 230 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 279 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 385 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 258 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 263 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 246 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 814 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 508 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 982 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 495 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 478 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 501 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 500 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 485 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 681 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 794 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 505 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 533 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 546 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 794 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 505 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 980 B

9597
luci2/luci2/htdocs/luci2/jquery-1.9.1.js vendored Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,261 @@
// Peity jQuery plugin version 1.2.0
// (c) 2013 Ben Pickles
//
// http://benpickles.github.com/peity
//
// Released under MIT license.
(function($, document, Math, devicePixelRatio) {
var canvasSupported = document.createElement("canvas").getContext
var peity = $.fn.peity = function(type, options) {
if (canvasSupported) {
this.each(function() {
var $this = $(this)
var chart = $this.data("peity")
if (chart) {
if (type) chart.type = type
$.extend(chart.opts, options)
chart.draw()
} else {
var defaults = peity.defaults[type]
var data = {}
$.each($this.data(), function(name, value) {
if (name in defaults) data[name] = value
})
var opts = $.extend({}, defaults, data, options)
var chart = new Peity($this, type, opts)
chart.draw()
$this
.change(function() { chart.draw() })
.data("peity", chart)
}
});
}
return this;
};
var Peity = function($el, type, opts) {
this.$el = $el
this.type = type
this.opts = opts
}
var PeityPrototype = Peity.prototype
PeityPrototype.colours = function() {
var colours = this.opts.colours
var func = colours
if (!$.isFunction(func)) {
func = function(_, i) {
return colours[i % colours.length]
}
}
return func
}
PeityPrototype.draw = function() {
peity.graphers[this.type].call(this, this.opts)
}
PeityPrototype.prepareCanvas = function(width, height) {
var canvas = this.canvas
var $canvas
if (canvas) {
this.context.clearRect(0, 0, canvas.width, canvas.height)
$canvas = $(canvas)
} else {
$canvas = $("<canvas>").css({
height: height,
width: width
}).addClass("peity").data("peity", this)
this.canvas = canvas = $canvas[0]
this.context = canvas.getContext("2d")
this.$el.hide().after(canvas)
}
canvas.height = $canvas.height() * devicePixelRatio
canvas.width = $canvas.width() * devicePixelRatio
return canvas
}
PeityPrototype.values = function() {
return $.map(this.$el.text().split(this.opts.delimiter), function(value) {
return parseFloat(value)
})
}
peity.defaults = {}
peity.graphers = {}
peity.register = function(type, defaults, grapher) {
this.defaults[type] = defaults
this.graphers[type] = grapher
}
peity.register(
'pie',
{
colours: ["#ff9900", "#fff4dd", "#ffc66e"],
delimiter: null,
diameter: 16
},
function(opts) {
if (!opts.delimiter) {
var delimiter = this.$el.text().match(/[^0-9\.]/)
opts.delimiter = delimiter ? delimiter[0] : ","
}
var values = this.values()
if (opts.delimiter == "/") {
var v1 = values[0]
var v2 = values[1]
values = [v1, v2 - v1]
}
var i = 0
var length = values.length
var sum = 0
for (; i < length; i++) {
sum += values[i]
}
var canvas = this.prepareCanvas(opts.width || opts.diameter, opts.height || opts.diameter)
var context = this.context
var width = canvas.width
var height = canvas.height
var radius = Math.min(width, height) / 2
var pi = Math.PI
var colours = this.colours()
context.save()
context.translate(width / 2, height / 2)
context.rotate(-pi / 2)
for (i = 0; i < length; i++) {
var value = values[i]
var slice = (value / sum) * pi * 2
context.beginPath()
context.moveTo(0, 0)
context.arc(0, 0, radius, 0, slice, false)
context.fillStyle = colours.call(this, value, i, values)
context.fill()
context.rotate(slice)
}
context.restore()
}
)
peity.register(
"line",
{
colour: "#c6d9fd",
strokeColour: "#4d89f9",
strokeWidth: 1,
delimiter: ",",
height: 16,
max: null,
min: 0,
width: 32
},
function(opts) {
var values = this.values()
if (values.length == 1) values.push(values[0])
var max = Math.max.apply(Math, values.concat([opts.max]));
var min = Math.min.apply(Math, values.concat([opts.min]))
var canvas = this.prepareCanvas(opts.width, opts.height)
var context = this.context
var width = canvas.width
var height = canvas.height
var xQuotient = width / (values.length - 1)
var yQuotient = height / (max - min)
var coords = [];
var i;
context.beginPath();
context.moveTo(0, height + (min * yQuotient))
for (i = 0; i < values.length; i++) {
var x = i * xQuotient
var y = height - (yQuotient * (values[i] - min))
coords.push({ x: x, y: y });
context.lineTo(x, y);
}
context.lineTo(width, height + (min * yQuotient))
context.fillStyle = opts.colour;
context.fill();
if (opts.strokeWidth) {
context.beginPath();
context.moveTo(0, coords[0].y);
for (i = 0; i < coords.length; i++) {
context.lineTo(coords[i].x, coords[i].y);
}
context.lineWidth = opts.strokeWidth * devicePixelRatio;
context.strokeStyle = opts.strokeColour;
context.stroke();
}
}
);
peity.register(
'bar',
{
colours: ["#4D89F9"],
delimiter: ",",
height: 16,
max: null,
min: 0,
spacing: devicePixelRatio,
width: 32
},
function(opts) {
var values = this.values()
var max = Math.max.apply(Math, values.concat([opts.max]));
var min = Math.min.apply(Math, values.concat([opts.min]))
var canvas = this.prepareCanvas(opts.width, opts.height)
var context = this.context
var width = canvas.width
var height = canvas.height
var yQuotient = height / (max - min)
var space = opts.spacing
var xQuotient = (width + space) / values.length
var colours = this.colours()
for (var i = 0; i < values.length; i++) {
var value = values[i]
var y = height - (yQuotient * (value - min))
var h
if (value == 0) {
if (min >= 0 || max > 0) y -= 1
h = 1
} else {
h = yQuotient * values[i]
}
context.fillStyle = colours.call(this, value, i, values)
context.fillRect(i * xQuotient, y, xQuotient - space, h)
}
}
);
})(jQuery, document, Math, window.devicePixelRatio || 1);

View file

@ -0,0 +1,705 @@
/*
LuCI2 - OpenWrt Web Interface
Copyright 2013-2014 Jo-Philipp Wich <jow@openwrt.org>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
*/
String.prototype.format = function()
{
var html_esc = [/&/g, '&#38;', /"/g, '&#34;', /'/g, '&#39;', /</g, '&#60;', />/g, '&#62;'];
var quot_esc = [/"/g, '&#34;', /'/g, '&#39;'];
function esc(s, r) {
for( var i = 0; i < r.length; i += 2 )
s = s.replace(r[i], r[i+1]);
return s;
}
var str = this;
var out = '';
var re = /^(([^%]*)%('.|0|\x20)?(-)?(\d+)?(\.\d+)?(%|b|c|d|u|f|o|s|x|X|q|h|j|t|m))/;
var a = b = [], numSubstitutions = 0, numMatches = 0;
while ((a = re.exec(str)) != null)
{
var m = a[1];
var leftpart = a[2], pPad = a[3], pJustify = a[4], pMinLength = a[5];
var pPrecision = a[6], pType = a[7];
numMatches++;
if (pType == '%')
{
subst = '%';
}
else
{
if (numSubstitutions < arguments.length)
{
var param = arguments[numSubstitutions++];
var pad = '';
if (pPad && pPad.substr(0,1) == "'")
pad = leftpart.substr(1,1);
else if (pPad)
pad = pPad;
var justifyRight = true;
if (pJustify && pJustify === "-")
justifyRight = false;
var minLength = -1;
if (pMinLength)
minLength = parseInt(pMinLength);
var precision = -1;
if (pPrecision && pType == 'f')
precision = parseInt(pPrecision.substring(1));
var subst = param;
switch(pType)
{
case 'b':
subst = (parseInt(param) || 0).toString(2);
break;
case 'c':
subst = String.fromCharCode(parseInt(param) || 0);
break;
case 'd':
subst = (parseInt(param) || 0);
break;
case 'u':
subst = Math.abs(parseInt(param) || 0);
break;
case 'f':
subst = (precision > -1)
? ((parseFloat(param) || 0.0)).toFixed(precision)
: (parseFloat(param) || 0.0);
break;
case 'o':
subst = (parseInt(param) || 0).toString(8);
break;
case 's':
subst = param;
break;
case 'x':
subst = ('' + (parseInt(param) || 0).toString(16)).toLowerCase();
break;
case 'X':
subst = ('' + (parseInt(param) || 0).toString(16)).toUpperCase();
break;
case 'h':
subst = esc(param, html_esc);
break;
case 'q':
subst = esc(param, quot_esc);
break;
case 'j':
subst = String.serialize(param);
break;
case 't':
var td = 0;
var th = 0;
var tm = 0;
var ts = (param || 0);
if (ts > 60) {
tm = Math.floor(ts / 60);
ts = (ts % 60);
}
if (tm > 60) {
th = Math.floor(tm / 60);
tm = (tm % 60);
}
if (th > 24) {
td = Math.floor(th / 24);
th = (th % 24);
}
subst = (td > 0)
? '%dd %dh %dm %ds'.format(td, th, tm, ts)
: '%dh %dm %ds'.format(th, tm, ts);
break;
case 'm':
var mf = pMinLength ? parseInt(pMinLength) : 1000;
var pr = pPrecision ? Math.floor(10*parseFloat('0'+pPrecision)) : 2;
var i = 0;
var val = parseFloat(param || 0);
var units = [ '', 'K', 'M', 'G', 'T', 'P', 'E' ];
for (i = 0; (i < units.length) && (val > mf); i++)
val /= mf;
subst = val.toFixed(pr) + ' ' + units[i];
break;
}
subst = (typeof(subst) == 'undefined') ? '' : subst.toString();
if (minLength > 0 && pad.length > 0)
for (var i = 0; i < (minLength - subst.length); i++)
subst = justifyRight ? (pad + subst) : (subst + pad);
}
}
out += leftpart + subst;
str = str.substr(m.length);
}
return out + str;
}
if (!window.location.origin)
window.location.origin = '%s//%s%s'.format(
window.location.protocol,
window.location.hostname,
(window.location.port ? ':' + window.location.port : '')
);
function LuCI2()
{
var L = this;
var Class = function() { };
Class.extend = function(properties)
{
Class.initializing = true;
var prototype = new this();
var superprot = this.prototype;
Class.initializing = false;
$.extend(prototype, properties, {
callSuper: function() {
var args = [ ];
var meth = arguments[0];
if (typeof(superprot[meth]) != 'function')
return undefined;
for (var i = 1; i < arguments.length; i++)
args.push(arguments[i]);
return superprot[meth].apply(this, args);
}
});
function _class()
{
this.options = arguments[0] || { };
if (!Class.initializing && typeof(this.init) == 'function')
this.init.apply(this, arguments);
}
_class.prototype = prototype;
_class.prototype.constructor = _class;
_class.extend = Class.extend;
return _class;
};
Class.require = function(name)
{
var path = '/' + name.replace(/\./g, '/') + '.js';
return $.ajax(path, {
method: 'GET',
async: false,
cache: true,
dataType: 'text'
}).then(function(text) {
var code = '%s\n\n//@ sourceURL=%s/%s'.format(text, window.location.origin, path);
var construct = eval(code);
var parts = name.split(/\./);
var cparent = L.Class || (L.Class = { });
for (var i = 1; i < parts.length - 1; i++)
{
cparent = cparent[parts[i]];
if (!cparent)
throw "Missing parent class";
}
cparent[parts[i]] = construct;
});
};
Class.instantiate = function(name)
{
Class.require(name).then(function() {
var parts = name.split(/\./);
var iparent = L;
var construct = L.Class;
for (var i = 1; i < parts.length - 1; i++)
{
iparent = iparent[parts[i]];
construct = construct[parts[i]];
if (!iparent)
throw "Missing parent class";
}
if (construct[parts[i]])
iparent[parts[i]] = new construct[parts[i]]();
});
};
this.defaults = function(obj, def)
{
for (var key in def)
if (typeof(obj[key]) == 'undefined')
obj[key] = def[key];
return obj;
};
this.isDeferred = function(x)
{
return (typeof(x) == 'object' &&
typeof(x.then) == 'function' &&
typeof(x.promise) == 'function');
};
this.deferrable = function()
{
if (this.isDeferred(arguments[0]))
return arguments[0];
var d = $.Deferred();
d.resolve.apply(d, arguments);
return d.promise();
};
this.i18n = {
loaded: false,
catalog: { },
plural: function(n) { return 0 + (n != 1) },
init: function() {
if (L.i18n.loaded)
return;
var lang = (navigator.userLanguage || navigator.language || 'en').toLowerCase();
var langs = (lang.indexOf('-') > -1) ? [ lang, lang.split(/-/)[0] ] : [ lang ];
for (var i = 0; i < langs.length; i++)
$.ajax('%s/i18n/base.%s.json'.format(L.globals.resource, langs[i]), {
async: false,
cache: true,
dataType: 'json',
success: function(data) {
$.extend(L.i18n.catalog, data);
var pe = L.i18n.catalog[''];
if (pe)
{
delete L.i18n.catalog[''];
try {
var pf = new Function('n', 'return 0 + (' + pe + ')');
L.i18n.plural = pf;
} catch (e) { };
}
}
});
L.i18n.loaded = true;
}
};
this.tr = function(msgid)
{
L.i18n.init();
var msgstr = L.i18n.catalog[msgid];
if (typeof(msgstr) == 'undefined')
return msgid;
else if (typeof(msgstr) == 'string')
return msgstr;
else
return msgstr[0];
};
this.trp = function(msgid, msgid_plural, count)
{
L.i18n.init();
var msgstr = L.i18n.catalog[msgid];
if (typeof(msgstr) == 'undefined')
return (count == 1) ? msgid : msgid_plural;
else if (typeof(msgstr) == 'string')
return msgstr;
else
return msgstr[L.i18n.plural(count)];
};
this.trc = function(msgctx, msgid)
{
L.i18n.init();
var msgstr = L.i18n.catalog[msgid + '\u0004' + msgctx];
if (typeof(msgstr) == 'undefined')
return msgid;
else if (typeof(msgstr) == 'string')
return msgstr;
else
return msgstr[0];
};
this.trcp = function(msgctx, msgid, msgid_plural, count)
{
L.i18n.init();
var msgstr = L.i18n.catalog[msgid + '\u0004' + msgctx];
if (typeof(msgstr) == 'undefined')
return (count == 1) ? msgid : msgid_plural;
else if (typeof(msgstr) == 'string')
return msgstr;
else
return msgstr[L.i18n.plural(count)];
};
this.setHash = function(key, value)
{
var h = '';
var data = this.getHash(undefined);
if (typeof(value) == 'undefined')
delete data[key];
else
data[key] = value;
var keys = [ ];
for (var k in data)
keys.push(k);
keys.sort();
for (var i = 0; i < keys.length; i++)
{
if (i > 0)
h += ',';
h += keys[i] + ':' + data[keys[i]];
}
if (h.length)
location.hash = '#' + h;
else
location.hash = '';
};
this.getHash = function(key)
{
var data = { };
var tuples = (location.hash || '#').substring(1).split(/,/);
for (var i = 0; i < tuples.length; i++)
{
var tuple = tuples[i].split(/:/);
if (tuple.length == 2)
data[tuple[0]] = tuple[1];
}
if (typeof(key) != 'undefined')
return data[key];
return data;
};
this.toArray = function(x)
{
switch (typeof(x))
{
case 'number':
case 'boolean':
return [ x ];
case 'string':
var r = [ ];
var l = x.split(/\s+/);
for (var i = 0; i < l.length; i++)
if (l[i].length > 0)
r.push(l[i]);
return r;
case 'object':
if ($.isArray(x))
{
var r = [ ];
for (var i = 0; i < x.length; i++)
r.push(x[i]);
return r;
}
else if ($.isPlainObject(x))
{
var r = [ ];
for (var k in x)
if (x.hasOwnProperty(k))
r.push(k);
return r.sort();
}
}
return [ ];
};
this.toObject = function(x)
{
switch (typeof(x))
{
case 'number':
case 'boolean':
return { x: true };
case 'string':
var r = { };
var l = x.split(/\x+/);
for (var i = 0; i < l.length; i++)
if (l[i].length > 0)
r[l[i]] = true;
return r;
case 'object':
if ($.isArray(x))
{
var r = { };
for (var i = 0; i < x.length; i++)
r[x[i]] = true;
return r;
}
else if ($.isPlainObject(x))
{
return x;
}
}
return { };
};
this.filterArray = function(array, item)
{
if (!$.isArray(array))
return [ ];
for (var i = 0; i < array.length; i++)
if (array[i] === item)
array.splice(i--, 1);
return array;
};
this.toClassName = function(str, suffix)
{
var n = '';
var l = str.split(/[\/.]/);
for (var i = 0; i < l.length; i++)
if (l[i].length > 0)
n += l[i].charAt(0).toUpperCase() + l[i].substr(1).toLowerCase();
if (typeof(suffix) == 'string')
n += suffix;
return n;
};
this.toColor = function(str)
{
if (typeof(str) != 'string' || str.length == 0)
return '#CCCCCC';
if (str == 'wan')
return '#F09090';
else if (str == 'lan')
return '#90F090';
var i = 0, hash = 0;
while (i < str.length)
hash = str.charCodeAt(i++) + ((hash << 5) - hash);
var r = (hash & 0xFF) % 128;
var g = ((hash >> 8) & 0xFF) % 128;
var min = 0;
var max = 128;
if ((r + g) < 128)
min = 128 - r - g;
else
max = 255 - r - g;
var b = min + (((hash >> 16) & 0xFF) % (max - min));
return '#%02X%02X%02X'.format(0xFF - r, 0xFF - g, 0xFF - b);
};
this.parseIPv4 = function(str)
{
if ((typeof(str) != 'string' && !(str instanceof String)) ||
!str.match(/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/))
return undefined;
var num = [ ];
var parts = str.split(/\./);
for (var i = 0; i < parts.length; i++)
{
var n = parseInt(parts[i], 10);
if (isNaN(n) || n > 255)
return undefined;
num.push(n);
}
return num;
};
this.parseIPv6 = function(str)
{
if ((typeof(str) != 'string' && !(str instanceof String)) ||
!str.match(/^[a-fA-F0-9:]+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})?$/))
return undefined;
var parts = str.split(/::/);
if (parts.length == 0 || parts.length > 2)
return undefined;
var lnum = [ ];
if (parts[0].length > 0)
{
var left = parts[0].split(/:/);
for (var i = 0; i < left.length; i++)
{
var n = parseInt(left[i], 16);
if (isNaN(n))
return undefined;
lnum.push((n / 256) >> 0);
lnum.push(n % 256);
}
}
var rnum = [ ];
if (parts.length > 1 && parts[1].length > 0)
{
var right = parts[1].split(/:/);
for (var i = 0; i < right.length; i++)
{
if (right[i].indexOf('.') > 0)
{
var addr = L.parseIPv4(right[i]);
if (!addr)
return undefined;
rnum.push.apply(rnum, addr);
continue;
}
var n = parseInt(right[i], 16);
if (isNaN(n))
return undefined;
rnum.push((n / 256) >> 0);
rnum.push(n % 256);
}
}
if (rnum.length > 0 && (lnum.length + rnum.length) > 15)
return undefined;
var num = [ ];
num.push.apply(num, lnum);
for (var i = 0; i < (16 - lnum.length - rnum.length); i++)
num.push(0);
num.push.apply(num, rnum);
if (num.length > 16)
return undefined;
return num;
};
this.isNetmask = function(addr)
{
if (!$.isArray(addr))
return false;
var c;
for (c = 0; (c < addr.length) && (addr[c] == 255); c++);
if (c == addr.length)
return true;
if ((addr[c] == 254) || (addr[c] == 252) || (addr[c] == 248) ||
(addr[c] == 240) || (addr[c] == 224) || (addr[c] == 192) ||
(addr[c] == 128) || (addr[c] == 0))
{
for (c++; (c < addr.length) && (addr[c] == 0); c++);
if (c == addr.length)
return true;
}
return false;
};
this.globals = {
timeout: 15000,
resource: '/luci2',
sid: '00000000000000000000000000000000'
};
Class.instantiate('luci2.rpc');
Class.instantiate('luci2.uci');
Class.instantiate('luci2.network');
Class.instantiate('luci2.wireless');
Class.instantiate('luci2.firewall');
Class.instantiate('luci2.system');
Class.instantiate('luci2.session');
Class.instantiate('luci2.ui');
Class.instantiate('luci2.cbi');
};

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,90 @@
L.network.Protocol.extend({
protocol: '6in4',
description: L.tr('IPv6-in-IPv4 (RFC4213)'),
tunnel: true,
virtual: true,
populateForm: function(section, iface)
{
var wan = L.network.findWAN();
section.taboption('general', L.cbi.InputValue, 'ipaddr', {
caption: L.tr('Local IPv4 address'),
description: L.tr('Leave empty to use the current WAN address'),
datatype: 'ip4addr',
placeholder: wan ? wan.getIPv4Addrs()[0] : undefined,
optional: true
});
section.taboption('general', L.cbi.InputValue, 'peeraddr', {
caption: L.tr('Remote IPv4 address'),
description: L.tr('This is usually the address of the nearest PoP operated by the tunnel broker'),
datatype: 'ip4addr',
optional: false
});
section.taboption('general', L.cbi.InputValue, 'ip6addr', {
caption: L.tr('Local IPv6 address'),
description: L.tr('This is the local endpoint address assigned by the tunnel broker'),
datatype: 'cidr6',
optional: false
});
section.taboption('general', L.cbi.InputValue, 'ip6prefix', {
caption: L.tr('IPv6 routed prefix'),
description: L.tr('This is the prefix routed to you by the tunnel broker for use by clients'),
datatype: 'cidr6',
optional: true
});
var update = section.taboption('general', L.cbi.CheckboxValue, '_update', {
caption: L.tr('Dynamic tunnel'),
description: L.tr('Enable HE.net dynamic endpoint update'),
enabled: '1',
disabled: '0'
});
update.save = function(sid) { };
update.ucivalue = function(sid) {
var n = parseInt(this.ownerMap.get('network', sid, 'tunnelid'));
return !isNaN(n);
};
section.taboption('general', L.cbi.InputValue, 'tunnelid', {
caption: L.tr('Tunnel ID'),
datatype: 'uinteger',
optional: false,
keep: false
}).depends('_update', true);
section.taboption('general', L.cbi.InputValue, 'username', {
caption: L.tr('HE.net user ID'),
description: L.tr('The login name of the HE.net account'),
datatype: 'string',
optional: false,
keep: false
}).depends('_update', true);
section.taboption('general', L.cbi.PasswordValue, 'password', {
caption: L.tr('Password'),
description: L.tr('Tunnel update key or HE.net account password'),
optional: false,
keep: false
}).depends('_update', true);
section.taboption('advanced', L.cbi.CheckboxValue, 'defaultroute', {
caption: L.tr('Default route'),
description: L.tr('Create IPv6 default route via tunnel'),
optional: true,
initial: true
});
section.taboption('advanced', L.cbi.InputValue, 'ttl', {
caption: L.tr('Override TTL'),
description: L.tr('Specifies the Time-to-Live on the tunnel interface'),
datatype: 'range(1,255)',
placeholder: 64,
optional: true
});
}
});

View file

@ -0,0 +1,63 @@
L.network.Protocol.extend({
protocol: '6rd',
description: L.tr('IPv6-over-IPv4 (6rd)'),
tunnel: true,
virtual: true,
populateForm: function(section, iface)
{
var wan = L.network.findWAN();
section.taboption('general', L.cbi.InputValue, 'peeraddr', {
caption: L.tr('6RD Gateway'),
datatype: 'ip4addr',
optional: false
});
section.taboption('general', L.cbi.InputValue, 'ipaddr', {
caption: L.tr('Local IPv4 address'),
description: L.tr('Leave empty to use the current WAN address'),
datatype: 'ip4addr',
placeholder: wan ? wan.getIPv4Addrs()[0] : undefined,
optional: true
});
section.taboption('general', L.cbi.InputValue, 'ip4prefixlen', {
caption: L.tr('IPv4 prefix length'),
description: L.tr('The length of the IPv4 prefix in bits, the remainder is used in the IPv6 addresses'),
datatype: 'range(0, 32)',
placeholder: 0,
optional: true
});
section.taboption('general', L.cbi.InputValue, 'ip6prefix', {
caption: L.tr('IPv6 prefix'),
description: L.tr('The IPv6 prefix assigned to the provider, usually ends with "::"'),
datatype: 'ip6addr',
optional: false
});
section.taboption('general', L.cbi.InputValue, 'ip6prefixlen', {
caption: L.tr('IPv6 prefix length'),
description: L.tr('The length of the IPv6 prefix in bits'),
datatype: 'range(0, 128)',
placeholder: 16,
optional: true
});
section.taboption('advanced', L.cbi.CheckboxValue, 'defaultroute', {
caption: L.tr('Default route'),
description: L.tr('Create IPv6 default route via tunnel'),
optional: true,
initial: true
});
section.taboption('advanced', L.cbi.InputValue, 'ttl', {
caption: L.tr('Override TTL'),
description: L.tr('Specifies the Time-to-Live on the tunnel interface'),
datatype: 'range(1,255)',
placeholder: 64,
optional: true
});
}
});

View file

@ -0,0 +1,35 @@
L.network.Protocol.extend({
protocol: '6to4',
description: L.tr('IPv6-over-IPv4 (6to4)'),
tunnel: true,
virtual: true,
populateForm: function(section, iface)
{
section.taboption('general', L.cbi.InputValue, 'ipaddr', {
caption: L.tr('Local IPv4 address'),
description: L.tr('Leave empty to use the current WAN address'),
datatype: 'ip4addr',
optional: true
}).load = function() {
var wan = L.network.findWAN();
if (wan)
this.options.placeholder = wan.getIPv4Addrs()[0];
};
section.taboption('advanced', L.cbi.CheckboxValue, 'defaultroute', {
caption: L.tr('Default route'),
description: L.tr('Create IPv6 default route via tunnel'),
optional: true,
initial: true
});
section.taboption('advanced', L.cbi.InputValue, 'ttl', {
caption: L.tr('Override TTL'),
description: L.tr('Specifies the Time-to-Live on the tunnel interface'),
datatype: 'range(1,255)',
placeholder: 64,
optional: true
});
}
});

View file

@ -0,0 +1,60 @@
L.network.Protocol.extend({
protocol: 'dhcp',
description: L.tr('DHCP client'),
tunnel: false,
virtual: false,
populateForm: function(section, iface)
{
section.taboption('general', L.cbi.InputValue, 'hostname', {
caption: L.tr('Hostname'),
description: L.tr('Hostname to send when requesting DHCP'),
datatype: 'hostname',
optional: true
}).load = function() {
var self = this;
return L.system.getBoardInfo().then(function(info) {
self.options.placeholder = info.hostname;
});
};
section.taboption('advanced', L.cbi.CheckboxValue, 'broadcast', {
caption: L.tr('Use broadcast'),
description: L.tr('Required for certain ISPs, e.g. Charter with DOCSIS3'),
optional: true
});
section.taboption('advanced', L.cbi.CheckboxValue, 'defaultroute', {
caption: L.tr('Use gateway'),
description: L.tr('Create default route via DHCP gateway'),
optional: true,
initial: true
});
section.taboption('advanced', L.cbi.CheckboxValue, 'peerdns', {
caption: L.tr('Use DNS'),
description: L.tr('Use DNS servers advertised by DHCP'),
optional: true,
initial: true
});
section.taboption('advanced', L.cbi.DynamicList, 'dns', {
caption: L.tr('Custom DNS'),
description: L.tr('Use custom DNS servers instead of DHCP ones'),
datatype: 'ipaddr',
optional: true
}).depends('peerdns', false);
section.taboption('advanced', L.cbi.InputValue, 'clientid', {
caption: L.tr('Client ID'),
description: L.tr('Client ID to send when requesting DHCP'),
optional: true
});
section.taboption('advanced', L.cbi.InputValue, 'vendorid', {
caption: L.tr('Vendor Class'),
description: L.tr('Vendor Class to send when requesting DHCP'),
optional: true
});
}
});

View file

@ -0,0 +1,59 @@
L.network.Protocol.extend({
protocol: 'dhcpv6',
description: L.tr('DHCPv6 client / IPv6 autoconfig'),
tunnel: false,
virtual: false,
populateForm: function(section, iface)
{
section.taboption('general', L.cbi.ListValue, 'reqaddress', {
caption: L.tr('Request IPv6 address'),
initial: 'try'
}).value('try', L.tr('Attempt DHCPv6, fallback to RA'))
.value('force', L.tr('Force DHCPv6'))
.value('none', L.tr('RA only'));
section.taboption('general', L.cbi.ComboBox, 'reqprefix', {
caption: L.tr('Request IPv6 prefix'),
description: L.tr('Specifies the requested prefix length'),
initial: 'auto',
datatype: 'or("auto", "no", range(32, 64))'
}).value('auto', L.tr('automatic'))
.value('no', L.tr('disabled'))
.value('48').value('52').value('56').value('60').value('64');
section.taboption('general', L.cbi.InputValue, 'ip6prefix', {
caption: L.tr('Custom prefix'),
description: L.tr('Specifies an additional custom IPv6 prefix for distribution to clients'),
datatype: 'ip6addr',
optional: true
});
section.taboption('advanced', L.cbi.CheckboxValue, 'defaultroute', {
caption: L.tr('Default route'),
description: L.tr('Create IPv6 default route via tunnel'),
optional: true,
initial: true
});
section.taboption('advanced', L.cbi.CheckboxValue, 'peerdns', {
caption: L.tr('Use DNS'),
description: L.tr('Use DNS servers advertised by DHCPv6'),
optional: true,
initial: true
});
section.taboption('advanced', L.cbi.DynamicList, 'dns', {
caption: L.tr('Custom DNS'),
description: L.tr('Use custom DNS servers instead of DHCPv6 ones'),
datatype: 'ipaddr',
optional: true
}).depends('peerdns', false);
section.taboption('advanced', L.cbi.InputValue, 'clientid', {
caption: L.tr('Client ID'),
description: L.tr('Client ID to send when requesting DHCPv6'),
optional: true
});
}
});

View file

@ -0,0 +1,46 @@
L.network.Protocol.extend({
protocol: 'dslite',
description: L.tr('Dual-Stack Lite (RFC6333)'),
tunnel: true,
virtual: true,
populateForm: function(section, iface)
{
var wan6 = L.network.findWAN6();
section.taboption('general', L.cbi.InputValue, 'peeraddr', {
caption: L.tr('DS-Lite AFTR address'),
datatype: 'ip6addr',
optional: false
});
section.taboption('general', L.cbi.InputValue, 'ip6addr', {
caption: L.tr('Local IPv6 address'),
description: L.tr('Leave empty to use the current WAN address'),
datatype: 'ip6addr',
placeholder: wan6 ? wan6.getIPv6Addrs()[0] : undefined,
optional: true
});
section.taboption('advanced', L.cbi.NetworkList, 'tunlink', {
caption: L.tr('Tunnel Link'),
initial: wan6 ? wan6.name() : undefined,
optional: true
});
section.taboption('advanced', L.cbi.CheckboxValue, 'defaultroute', {
caption: L.tr('Default route'),
description: L.tr('Create IPv4 default route via tunnel'),
optional: true,
initial: true
});
section.taboption('advanced', L.cbi.InputValue, 'ttl', {
caption: L.tr('Override TTL'),
description: L.tr('Specifies the Time-to-Live on the tunnel interface'),
datatype: 'range(1,255)',
placeholder: 64,
optional: true
});
}
});

View file

@ -0,0 +1,6 @@
L.network.Protocol.extend({
protocol: 'none',
description: L.tr('Unmanaged'),
tunnel: false,
virtual: false
});

View file

@ -0,0 +1,113 @@
L.network.Protocol.extend({
protocol: 'static',
description: L.tr('Static address'),
tunnel: false,
virtual: false,
_ev_broadcast: function(ev)
{
var self = ev.data.self;
var sid = ev.data.sid;
var i = ($('#' + self.ownerSection.id('field', sid, 'ipaddr')).val() || '').split(/\./);
var m = ($('#' + self.ownerSection.id('field', sid, 'netmask') + ' select').val() || '').split(/\./);
var I = 0;
var M = 0;
for (var n = 0; n < 4; n++)
{
i[n] = parseInt(i[n]);
m[n] = parseInt(m[n]);
if (isNaN(i[n]) || i[n] < 0 || i[n] > 255 ||
isNaN(m[n]) || m[n] < 0 || m[n] > 255)
return;
I |= (i[n] << ((3 - n) * 8));
M |= (m[n] << ((3 - n) * 8));
}
var B = I | ~M;
$('#' + self.section.id('field', sid, 'broadcast'))
.attr('placeholder', '%d.%d.%d.%d'.format(
(B >> 24) & 0xFF, (B >> 16) & 0xFF,
(B >> 8) & 0xFF, (B >> 0) & 0xFF
));
},
populateForm: function(section, iface)
{
var device = L.network.getDeviceByInterface(iface);
section.taboption('general', L.cbi.InputValue, 'ipaddr', {
caption: L.tr('IPv4 address'),
datatype: 'ip4addr',
optional: true
}).on('blur validate', this._ev_broadcast);
section.taboption('general', L.cbi.ComboBox, 'netmask', {
caption: L.tr('IPv4 netmask'),
datatype: 'ip4addr',
optional: true
}).on('blur validate', this._ev_broadcast)
.value('255.255.255.0')
.value('255.255.0.0')
.value('255.0.0.0');
section.taboption('general', L.cbi.InputValue, 'broadcast', {
caption: L.tr('IPv4 broadcast'),
datatype: 'ip4addr',
optional: true
});
section.taboption('general', L.cbi.InputValue, 'gateway', {
caption: L.tr('IPv4 gateway'),
datatype: 'ip4addr',
optional: true
});
section.taboption('general', L.cbi.DynamicList, 'dns', {
caption: L.tr('DNS servers'),
datatype: 'ipaddr',
optional: true
});
section.taboption('ipv6', L.cbi.ComboBox, 'ip6assign', {
caption: L.tr('IPv6 assignment length'),
description: L.tr('Assign a part of given length of every public IPv6-prefix to this interface'),
datatype: 'max(64)',
optional: true
}).value('', L.tr('disabled')).value('64');
var ip6hint = section.taboption('ipv6', L.cbi.InputValue, 'ip6hint', {
caption: L.tr('IPv6 assignment hint'),
description: L.tr('Assign prefix parts using this hexadecimal subprefix ID for this interface'),
optional: true
});
for (var i = 33; i <= 64; i++)
ip6hint.depends('ip6assign', i);
section.taboption('ipv6', L.cbi.InputValue, 'ip6addr', {
caption: L.tr('IPv6 address'),
datatype: 'ip6addr',
optional: true
}).depends('ip6assign', false);
section.taboption('ipv6', L.cbi.InputValue, 'ip6gw', {
caption: L.tr('IPv6 gateway'),
datatype: 'ip6addr',
optional: true
}).depends('ip6assign', false);
section.taboption('ipv6', L.cbi.InputValue, 'ip6prefix', {
caption: L.tr('IPv6 routed prefix'),
description: L.tr('Public prefix routed to this device for distribution to clients'),
datatype: 'ip6addr',
optional: true
}).depends('ip6assign', false);
}
});

View file

@ -0,0 +1,6 @@
/*! matchMedia() polyfill - Test a CSS media type/query in JS. Authors & copyright (c) 2012: Scott Jehl, Paul Irish, Nicholas Zakas. Dual MIT/BSD license */
/*! NOTE: If you're already including a window.matchMedia polyfill via Modernizr or otherwise, you don't need this part */
window.matchMedia=window.matchMedia||function(a){"use strict";var c,d=a.documentElement,e=d.firstElementChild||d.firstChild,f=a.createElement("body"),g=a.createElement("div");return g.id="mq-test-1",g.style.cssText="position:absolute;top:-100em",f.style.background="none",f.appendChild(g),function(a){return g.innerHTML='&shy;<style media="'+a+'"> #mq-test-1 { width: 42px; }</style>',d.insertBefore(f,e),c=42===g.offsetWidth,d.removeChild(f),{matches:c,media:a}}}(document);
/*! Respond.js v1.3.0: min/max-width media query polyfill. (c) Scott Jehl. MIT/GPLv2 Lic. j.mp/respondjs */
(function(a){"use strict";function x(){u(!0)}var b={};if(a.respond=b,b.update=function(){},b.mediaQueriesSupported=a.matchMedia&&a.matchMedia("only all").matches,!b.mediaQueriesSupported){var q,r,t,c=a.document,d=c.documentElement,e=[],f=[],g=[],h={},i=30,j=c.getElementsByTagName("head")[0]||d,k=c.getElementsByTagName("base")[0],l=j.getElementsByTagName("link"),m=[],n=function(){for(var b=0;l.length>b;b++){var c=l[b],d=c.href,e=c.media,f=c.rel&&"stylesheet"===c.rel.toLowerCase();d&&f&&!h[d]&&(c.styleSheet&&c.styleSheet.rawCssText?(p(c.styleSheet.rawCssText,d,e),h[d]=!0):(!/^([a-zA-Z:]*\/\/)/.test(d)&&!k||d.replace(RegExp.$1,"").split("/")[0]===a.location.host)&&m.push({href:d,media:e}))}o()},o=function(){if(m.length){var b=m.shift();v(b.href,function(c){p(c,b.href,b.media),h[b.href]=!0,a.setTimeout(function(){o()},0)})}},p=function(a,b,c){var d=a.match(/@media[^\{]+\{([^\{\}]*\{[^\}\{]*\})+/gi),g=d&&d.length||0;b=b.substring(0,b.lastIndexOf("/"));var h=function(a){return a.replace(/(url\()['"]?([^\/\)'"][^:\)'"]+)['"]?(\))/g,"$1"+b+"$2$3")},i=!g&&c;b.length&&(b+="/"),i&&(g=1);for(var j=0;g>j;j++){var k,l,m,n;i?(k=c,f.push(h(a))):(k=d[j].match(/@media *([^\{]+)\{([\S\s]+?)$/)&&RegExp.$1,f.push(RegExp.$2&&h(RegExp.$2))),m=k.split(","),n=m.length;for(var o=0;n>o;o++)l=m[o],e.push({media:l.split("(")[0].match(/(only\s+)?([a-zA-Z]+)\s?/)&&RegExp.$2||"all",rules:f.length-1,hasquery:l.indexOf("(")>-1,minw:l.match(/\(\s*min\-width\s*:\s*(\s*[0-9\.]+)(px|em)\s*\)/)&&parseFloat(RegExp.$1)+(RegExp.$2||""),maxw:l.match(/\(\s*max\-width\s*:\s*(\s*[0-9\.]+)(px|em)\s*\)/)&&parseFloat(RegExp.$1)+(RegExp.$2||"")})}u()},s=function(){var a,b=c.createElement("div"),e=c.body,f=!1;return b.style.cssText="position:absolute;font-size:1em;width:1em",e||(e=f=c.createElement("body"),e.style.background="none"),e.appendChild(b),d.insertBefore(e,d.firstChild),a=b.offsetWidth,f?d.removeChild(e):e.removeChild(b),a=t=parseFloat(a)},u=function(b){var h="clientWidth",k=d[h],m="CSS1Compat"===c.compatMode&&k||c.body[h]||k,n={},o=l[l.length-1],p=(new Date).getTime();if(b&&q&&i>p-q)return a.clearTimeout(r),r=a.setTimeout(u,i),void 0;q=p;for(var v in e)if(e.hasOwnProperty(v)){var w=e[v],x=w.minw,y=w.maxw,z=null===x,A=null===y,B="em";x&&(x=parseFloat(x)*(x.indexOf(B)>-1?t||s():1)),y&&(y=parseFloat(y)*(y.indexOf(B)>-1?t||s():1)),w.hasquery&&(z&&A||!(z||m>=x)||!(A||y>=m))||(n[w.media]||(n[w.media]=[]),n[w.media].push(f[w.rules]))}for(var C in g)g.hasOwnProperty(C)&&g[C]&&g[C].parentNode===j&&j.removeChild(g[C]);for(var D in n)if(n.hasOwnProperty(D)){var E=c.createElement("style"),F=n[D].join("\n");E.type="text/css",E.media=D,j.insertBefore(E,o.nextSibling),E.styleSheet?E.styleSheet.cssText=F:E.appendChild(c.createTextNode(F)),g.push(E)}},v=function(a,b){var c=w();c&&(c.open("GET",a,!0),c.onreadystatechange=function(){4!==c.readyState||200!==c.status&&304!==c.status||b(c.responseText)},4!==c.readyState&&c.send(null))},w=function(){var b=!1;try{b=new a.XMLHttpRequest}catch(c){b=new a.ActiveXObject("Microsoft.XMLHTTP")}return function(){return b}}();n(),b.update=n,a.addEventListener?a.addEventListener("resize",x,!1):a.attachEvent&&a.attachEvent("onresize",x)}})(this);

View file

@ -0,0 +1,188 @@
Class.extend({
_id: 1,
_batch: undefined,
_requests: { },
_call: function(req, cb)
{
var q = '';
if ($.isArray(req))
for (var i = 0; i < req.length; i++)
q += '%s%s.%s'.format(
q ? ';' : '/',
req[i].params[1],
req[i].params[2]
);
else
q += '/%s.%s'.format(req.params[1], req.params[2]);
return $.ajax('/ubus' + q, {
cache: false,
contentType: 'application/json',
data: JSON.stringify(req),
dataType: 'json',
type: 'POST',
timeout: L.globals.timeout,
_rpc_req: req
}).then(cb, cb);
},
_list_cb: function(msg)
{
var list = msg.result;
/* verify message frame */
if (typeof(msg) != 'object' || msg.jsonrpc != '2.0' || !msg.id || !$.isArray(list))
list = [ ];
return $.Deferred().resolveWith(this, [ list ]);
},
_call_cb: function(msg)
{
var data = [ ];
var type = Object.prototype.toString;
var reqs = this._rpc_req;
if (!$.isArray(reqs))
{
msg = [ msg ];
reqs = [ reqs ];
}
for (var i = 0; i < msg.length; i++)
{
/* fetch related request info */
var req = L.rpc._requests[reqs[i].id];
if (typeof(req) != 'object')
throw 'No related request for JSON response';
/* fetch response attribute and verify returned type */
var ret = undefined;
/* verify message frame */
if (typeof(msg[i]) == 'object' && msg[i].jsonrpc == '2.0')
if ($.isArray(msg[i].result) && msg[i].result[0] == 0)
ret = (msg[i].result.length > 1) ? msg[i].result[1] : msg[i].result[0];
if (req.expect)
{
for (var key in req.expect)
{
if (typeof(ret) != 'undefined' && key != '')
ret = ret[key];
if (typeof(ret) == 'undefined' || type.call(ret) != type.call(req.expect[key]))
ret = req.expect[key];
break;
}
}
/* apply filter */
if (typeof(req.filter) == 'function')
{
req.priv[0] = ret;
req.priv[1] = req.params;
ret = req.filter.apply(L.rpc, req.priv);
}
/* store response data */
if (typeof(req.index) == 'number')
data[req.index] = ret;
else
data = ret;
/* delete request object */
delete L.rpc._requests[reqs[i].id];
}
return $.Deferred().resolveWith(this, [ data ]);
},
list: function()
{
var params = [ ];
for (var i = 0; i < arguments.length; i++)
params[i] = arguments[i];
var msg = {
jsonrpc: '2.0',
id: this._id++,
method: 'list',
params: (params.length > 0) ? params : undefined
};
return this._call(msg, this._list_cb);
},
batch: function()
{
if (!$.isArray(this._batch))
this._batch = [ ];
},
flush: function()
{
if (!$.isArray(this._batch))
return L.deferrable([ ]);
var req = this._batch;
delete this._batch;
/* call rpc */
return this._call(req, this._call_cb);
},
declare: function(options)
{
var _rpc = this;
return function() {
/* build parameter object */
var p_off = 0;
var params = { };
if ($.isArray(options.params))
for (p_off = 0; p_off < options.params.length; p_off++)
params[options.params[p_off]] = arguments[p_off];
/* all remaining arguments are private args */
var priv = [ undefined, undefined ];
for (; p_off < arguments.length; p_off++)
priv.push(arguments[p_off]);
/* store request info */
var req = _rpc._requests[_rpc._id] = {
expect: options.expect,
filter: options.filter,
params: params,
priv: priv
};
/* build message object */
var msg = {
jsonrpc: '2.0',
id: _rpc._id++,
method: 'call',
params: [
L.globals.sid,
options.object,
options.method,
params
]
};
/* when a batch is in progress then store index in request data
* and push message object onto the stack */
if ($.isArray(_rpc._batch))
{
req.index = _rpc._batch.push(msg) - 1;
return L.deferrable(msg);
}
/* call rpc */
return _rpc._call(msg, _rpc._call_cb);
};
}
});

View file

@ -0,0 +1,78 @@
Class.extend({
login: L.rpc.declare({
object: 'session',
method: 'login',
params: [ 'username', 'password' ],
expect: { '': { } }
}),
access: L.rpc.declare({
object: 'session',
method: 'access',
params: [ 'scope', 'object', 'function' ],
expect: { access: false }
}),
isAlive: function()
{
return L.session.access('ubus', 'session', 'access');
},
startHeartbeat: function()
{
this._hearbeatInterval = window.setInterval(function() {
L.session.isAlive().then(function(alive) {
if (!alive)
{
L.session.stopHeartbeat();
L.ui.login(true);
}
});
}, L.globals.timeout * 2);
},
stopHeartbeat: function()
{
if (typeof(this._hearbeatInterval) != 'undefined')
{
window.clearInterval(this._hearbeatInterval);
delete this._hearbeatInterval;
}
},
aclCache: { },
callAccess: L.rpc.declare({
object: 'session',
method: 'access',
expect: { '': { } }
}),
callAccessCallback: function(acls)
{
L.session.aclCache = acls;
},
updateACLs: function()
{
return L.session.callAccess()
.then(L.session.callAccessCallback);
},
hasACL: function(scope, object, func)
{
var acls = L.session.aclCache;
if (typeof(func) == 'undefined')
return (acls && acls[scope] && acls[scope][object]);
if (acls && acls[scope] && acls[scope][object])
for (var i = 0; i < acls[scope][object].length; i++)
if (acls[scope][object][i] == func)
return true;
return false;
}
});

View file

@ -0,0 +1,82 @@
Class.extend({
getSystemInfo: L.rpc.declare({
object: 'system',
method: 'info',
expect: { '': { } }
}),
getBoardInfo: L.rpc.declare({
object: 'system',
method: 'board',
expect: { '': { } }
}),
getDiskInfo: L.rpc.declare({
object: 'luci2.system',
method: 'diskfree',
expect: { '': { } }
}),
getInfo: function(cb)
{
L.rpc.batch();
this.getSystemInfo();
this.getBoardInfo();
this.getDiskInfo();
return L.rpc.flush().then(function(info) {
var rv = { };
$.extend(rv, info[0]);
$.extend(rv, info[1]);
$.extend(rv, info[2]);
return rv;
});
},
initList: L.rpc.declare({
object: 'luci2.system',
method: 'init_list',
expect: { initscripts: [ ] },
filter: function(data) {
data.sort(function(a, b) { return (a.start || 0) - (b.start || 0) });
return data;
}
}),
initEnabled: function(init, cb)
{
return this.initList().then(function(list) {
for (var i = 0; i < list.length; i++)
if (list[i].name == init)
return !!list[i].enabled;
return false;
});
},
initRun: L.rpc.declare({
object: 'luci2.system',
method: 'init_action',
params: [ 'name', 'action' ],
filter: function(data) {
return (data == 0);
}
}),
initStart: function(init, cb) { return L.system.initRun(init, 'start', cb) },
initStop: function(init, cb) { return L.system.initRun(init, 'stop', cb) },
initRestart: function(init, cb) { return L.system.initRun(init, 'restart', cb) },
initReload: function(init, cb) { return L.system.initRun(init, 'reload', cb) },
initEnable: function(init, cb) { return L.system.initRun(init, 'enable', cb) },
initDisable: function(init, cb) { return L.system.initRun(init, 'disable', cb) },
performReboot: L.rpc.declare({
object: 'luci2.system',
method: 'reboot'
})
});

View file

@ -0,0 +1,20 @@
<p><%:Select the utility to run and click "Test" to perform the requested operation.%></p>
<p>
<table style="width:auto">
<tr>
<td>
<input type="text" id="host" value="openwrt.org" class="form-control" />
</td>
<td>
<div class="input-group">
<select id="tool" class="form-control"></select>
<div class="input-group-btn">
<button type="button" class="btn btn-primary" id="run"><%:Test%></button>
</div>
</div>
</td>
</tr>
</table>
</p>
<pre id="output" style="display:none"></pre>

View file

@ -0,0 +1 @@
<div id="map"></div>

View file

@ -0,0 +1 @@
<div id="map"></div>

View file

@ -0,0 +1 @@
<div id="map"></div>

View file

@ -0,0 +1 @@
<div id="map"></div>

View file

@ -0,0 +1 @@
<div id="map"></div>

View file

@ -0,0 +1 @@
<textarea readonly="readonly" wrap="off" id="syslog" class="form-control"><%:Collecting data...%></textarea>

View file

@ -0,0 +1,10 @@
<div id="system_status_table"></div>
<div id="memory_status_table"></div>
<div id="swap_status_table"></div>
<div id="disk_status_table"></div>
<div id="network_status_table"></div>
<div id="conntrack_status_table"></div>
<div id="lease_status_table"></div>
<div id="lease6_status_table"></div>
<div id="wifi_status_table"></div>
<div id="wifi_assoc_table"></div>

View file

@ -0,0 +1 @@
<div id="process_table"></div>

View file

@ -0,0 +1,3 @@
<div id="arp_table"></div>
<div id="route_table"></div>
<div id="route6_table"></div>

View file

@ -0,0 +1 @@
<textarea readonly="readonly" wrap="off" id="syslog" class="form-control"><%:Collecting data...%></textarea>

View file

@ -0,0 +1 @@
<div id="map"></div>

View file

@ -0,0 +1,6 @@
<div class="panel">
<textarea id="crontab" class="form-control" rows="10"></textarea>
</div>
<div class="pull-right">
<input class="btn btn-primary" type="button" id="btn_save" value="<%:Save%>" />
</div>

View file

@ -0,0 +1 @@
<div id="map"></div>

View file

@ -0,0 +1,71 @@
<ul class="nav nav-tabs">
<li class="active"><a href="#status" data-toggle="tab"><%:Packages%></a></li>
<li><a href="#config" data-toggle="tab"><%:Configuration%></a></li>
</ul>
<div class="tab-content">
<div class="tab-pane active" id="status">
<div class="form-horizontal">
<div class="form-group">
<label class="col-lg-2 control-label"><%:Used space%></label>
<div class="col-lg-5">
<div id="package_space" class="form-control-static"></div>
</div>
<div class="col-lg-5"></div>
</div>
<div class="form-group">
<label class="col-lg-2 control-label"><%:Update package lists%></label>
<div class="col-lg-5">
<button id="package_update" type="button" class="btn btn-default"><%:Start update …%></button>
</div>
<div class="col-lg-5"></div>
</div>
<div class="form-group">
<label class="col-lg-2 control-label"><%:Install package directly%></label>
<div class="col-lg-5">
<div class="input-group">
<input id="package_url" type="text" class="form-control" placeholder="<%:Enter URL or name …%>" />
<span class="input-group-btn">
<button id="package_install" type="button" class="btn btn-info">»</button>
</span>
</div>
</div>
<div class="col-lg-5"></div>
</div>
<div class="form-group">
<label class="col-lg-2 control-label"><%:Filter packages%></label>
<div class="col-lg-5">
<div class="input-group">
<input id="package_filter" type="text" class="form-control" placeholder="<%:Filter packages …%>" />
<span class="input-group-btn">
<button" type="button" class="btn btn-default">×</button>
</span>
</div>
<label><input type="checkbox" id="package_which" style="vertical-align: middle; margin-top: 0" /> <%:Display only installed packages%></label>
</div>
<div class="col-lg-5"></div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<div class="btn-group">
<button id="package_prev" type="button" class="btn btn-default">« 0 - 0</button>
<button id="package_next" type="button" class="btn btn-default">0 - 0 »</button>
</div>
</div>
<div class="panel-body" id="package_table"></div>
</div>
</div>
<div class="tab-pane" id="config">
<div class="panel panel-default">
<textarea class="form-control"></textarea>
</div>
<div class="text-right">
<button class="btn btn-primary" type="button"><%:Save%></button>
</div>
</div>
</div>

View file

@ -0,0 +1,21 @@
<ul class="nav nav-tabs">
<li class="active"><a href="#rclocal" data-toggle="tab"><%:Local Startup%></a></li>
<li><a href="#initscripts" data-toggle="tab"><%:Initscripts%></a></li>
</ul>
<div class="tab-content">
<div class="tab-pane active" id="rclocal">
<p><%:This is the content of /etc/rc.local. Insert your own commands here (in front of 'exit 0') to execute them at the end of the boot process.%></p>
<div class="panel panel-default">
<textarea id="rc_local" class="form-control" style="width:100%" rows="10"></textarea>
</div>
<div class="text-right">
<button type="button" class="btn btn-primary"><%:Save%></button>
</div>
</div>
<div class="tab-pane" id="initscripts">
<p><%:You can enable or disable installed init scripts here. Changes will applied after a device reboot. Warning: If you disable essential init scripts like "network", your device might become inaccessible!%></p>
<div id="init_table"></div>
</div>
</div>

View file

@ -0,0 +1 @@
<div id="map"></div>

View file

@ -0,0 +1,51 @@
<ul class="nav nav-tabs">
<li class="active"><a data-toggle="tab" href="#actions"><%:Actions%></a></li>
<li><a data-toggle="tab" href="#config"><%:Configuration%></a></li>
</ul>
<div class="tab-content">
<div class="tab-pane active" id="actions">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title"><%:Backup / Restore%></h3>
</div>
<div class="panel-body">
<p><%:Click "Generate archive" to download a tar archive of the current configuration files. To reset the firmware to its initial state, click "Perform reset" (only possible with squashfs images).%></p>
<p>
<form action="/cgi-bin/luci-backup" method="post" style="display:inline">
<input type="hidden" name="sessionid" />
<input class="btn btn-primary" type="button" id="btn_backup" value="<%:Generate archive%>" />
</form>
<input class="btn btn-danger" type="button" id="btn_reset" value="<%:Perform reset%>" />
</p>
<p><%:To restore configuration files, you can upload a previously generated backup archive here.%></p>
<p>
<input class="btn btn-primary" type="button" id="btn_restore" value="<%:Upload archive...%>" />
</p>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title"><%:Flash new firmware image%></h3>
</div>
<div class="panel-body">
<p><%:Upload a sysupgrade-compatible image here to replace the running firmware. Check "Keep settings" to retain the current configuration (requires an OpenWrt compatible firmware image).%></p>
<p>
<input class="btn btn-primary" type="button" id="btn_flash" value="<%:Flash image...%>" />
</p>
</div>
</div>
</div>
<div class="tab-pane" id="config">
<div class="panel">
<textarea class="form-control"></textarea>
</div>
<div class="pull-right">
<input class="btn btn-primary" type="button" id="btn_save" value="<%:Save%>" />
<input class="btn btn-default" type="button" id="btn_list" value="<%:Show current backup file list …%>" />
</div>
</div>
</div>

View file

@ -0,0 +1 @@
<div id="map"></div>

View file

@ -0,0 +1,532 @@
Class.extend({
init: function()
{
this.state = {
newidx: 0,
values: { },
creates: { },
changes: { },
deletes: { },
reorder: { }
};
},
callLoad: L.rpc.declare({
object: 'uci',
method: 'get',
params: [ 'config' ],
expect: { values: { } }
}),
callOrder: L.rpc.declare({
object: 'uci',
method: 'order',
params: [ 'config', 'sections' ]
}),
callAdd: L.rpc.declare({
object: 'uci',
method: 'add',
params: [ 'config', 'type', 'name', 'values' ],
expect: { section: '' }
}),
callSet: L.rpc.declare({
object: 'uci',
method: 'set',
params: [ 'config', 'section', 'values' ]
}),
callDelete: L.rpc.declare({
object: 'uci',
method: 'delete',
params: [ 'config', 'section', 'options' ]
}),
callApply: L.rpc.declare({
object: 'uci',
method: 'apply',
params: [ 'timeout', 'rollback' ]
}),
callConfirm: L.rpc.declare({
object: 'uci',
method: 'confirm'
}),
createSID: function(conf)
{
var v = this.state.values;
var n = this.state.creates;
var sid;
do {
sid = "new%06x".format(Math.random() * 0xFFFFFF);
} while ((n[conf] && n[conf][sid]) || (v[conf] && v[conf][sid]));
return sid;
},
reorderSections: function()
{
var v = this.state.values;
var n = this.state.creates;
var r = this.state.reorder;
if ($.isEmptyObject(r))
return L.deferrable();
L.rpc.batch();
/*
gather all created and existing sections, sort them according
to their index value and issue an uci order call
*/
for (var c in r)
{
var o = [ ];
if (n[c])
for (var s in n[c])
o.push(n[c][s]);
for (var s in v[c])
o.push(v[c][s]);
if (o.length > 0)
{
o.sort(function(a, b) {
return (a['.index'] - b['.index']);
});
var sids = [ ];
for (var i = 0; i < o.length; i++)
sids.push(o[i]['.name']);
this.callOrder(c, sids);
}
}
this.state.reorder = { };
return L.rpc.flush();
},
load: function(packages)
{
var self = this;
var seen = { };
var pkgs = [ ];
if (!$.isArray(packages))
packages = [ packages ];
L.rpc.batch();
for (var i = 0; i < packages.length; i++)
if (!seen[packages[i]] && !self.state.values[packages[i]])
{
pkgs.push(packages[i]);
seen[packages[i]] = true;
self.callLoad(packages[i]);
}
return L.rpc.flush().then(function(responses) {
for (var i = 0; i < responses.length; i++)
self.state.values[pkgs[i]] = responses[i];
return pkgs;
});
},
unload: function(packages)
{
if (!$.isArray(packages))
packages = [ packages ];
for (var i = 0; i < packages.length; i++)
{
delete this.state.values[packages[i]];
delete this.state.creates[packages[i]];
delete this.state.changes[packages[i]];
delete this.state.deletes[packages[i]];
}
},
add: function(conf, type, name)
{
var n = this.state.creates;
var sid = name || this.createSID(conf);
if (!n[conf])
n[conf] = { };
n[conf][sid] = {
'.type': type,
'.name': sid,
'.create': name,
'.anonymous': !name,
'.index': 1000 + this.state.newidx++
};
return sid;
},
remove: function(conf, sid)
{
var n = this.state.creates;
var c = this.state.changes;
var d = this.state.deletes;
/* requested deletion of a just created section */
if (n[conf] && n[conf][sid])
{
delete n[conf][sid];
}
else
{
if (c[conf])
delete c[conf][sid];
if (!d[conf])
d[conf] = { };
d[conf][sid] = true;
}
},
sections: function(conf, type, cb)
{
var sa = [ ];
var v = this.state.values[conf];
var n = this.state.creates[conf];
var c = this.state.changes[conf];
var d = this.state.deletes[conf];
if (!v)
return sa;
for (var s in v)
if (!d || d[s] !== true)
if (!type || v[s]['.type'] == type)
sa.push($.extend({ }, v[s], c ? c[s] : undefined));
if (n)
for (var s in n)
if (!type || n[s]['.type'] == type)
sa.push(n[s]);
sa.sort(function(a, b) {
return a['.index'] - b['.index'];
});
for (var i = 0; i < sa.length; i++)
sa[i]['.index'] = i;
if (typeof(cb) == 'function')
for (var i = 0; i < sa.length; i++)
cb.call(this, sa[i], sa[i]['.name']);
return sa;
},
get: function(conf, sid, opt)
{
var v = this.state.values;
var n = this.state.creates;
var c = this.state.changes;
var d = this.state.deletes;
if (typeof(sid) == 'undefined')
return undefined;
/* requested option in a just created section */
if (n[conf] && n[conf][sid])
{
if (!n[conf])
return undefined;
if (typeof(opt) == 'undefined')
return n[conf][sid];
return n[conf][sid][opt];
}
/* requested an option value */
if (typeof(opt) != 'undefined')
{
/* check whether option was deleted */
if (d[conf] && d[conf][sid])
{
if (d[conf][sid] === true)
return undefined;
for (var i = 0; i < d[conf][sid].length; i++)
if (d[conf][sid][i] == opt)
return undefined;
}
/* check whether option was changed */
if (c[conf] && c[conf][sid] && typeof(c[conf][sid][opt]) != 'undefined')
return c[conf][sid][opt];
/* return base value */
if (v[conf] && v[conf][sid])
return v[conf][sid][opt];
return undefined;
}
/* requested an entire section */
if (v[conf])
return v[conf][sid];
return undefined;
},
set: function(conf, sid, opt, val)
{
var v = this.state.values;
var n = this.state.creates;
var c = this.state.changes;
var d = this.state.deletes;
if (typeof(sid) == 'undefined' ||
typeof(opt) == 'undefined' ||
opt.charAt(0) == '.')
return;
if (n[conf] && n[conf][sid])
{
if (typeof(val) != 'undefined')
n[conf][sid][opt] = val;
else
delete n[conf][sid][opt];
}
else if (typeof(val) != 'undefined' && val !== '')
{
/* do not set within deleted section */
if (d[conf] && d[conf][sid] === true)
return;
/* only set in existing sections */
if (!v[conf] || !v[conf][sid])
return;
if (!c[conf])
c[conf] = { };
if (!c[conf][sid])
c[conf][sid] = { };
/* undelete option */
if (d[conf] && d[conf][sid])
d[conf][sid] = L.filterArray(d[conf][sid], opt);
c[conf][sid][opt] = val;
}
else
{
/* only delete in existing sections */
if (!v[conf] || !v[conf][sid])
return;
if (!d[conf])
d[conf] = { };
if (!d[conf][sid])
d[conf][sid] = [ ];
if (d[conf][sid] !== true)
d[conf][sid].push(opt);
}
},
unset: function(conf, sid, opt)
{
return this.set(conf, sid, opt, undefined);
},
get_first: function(conf, type, opt)
{
var sid = undefined;
L.uci.sections(conf, type, function(s) {
if (typeof(sid) != 'string')
sid = s['.name'];
});
return this.get(conf, sid, opt);
},
set_first: function(conf, type, opt, val)
{
var sid = undefined;
L.uci.sections(conf, type, function(s) {
if (typeof(sid) != 'string')
sid = s['.name'];
});
return this.set(conf, sid, opt, val);
},
unset_first: function(conf, type, opt)
{
return this.set_first(conf, type, opt, undefined);
},
swap: function(conf, sid1, sid2)
{
var s1 = this.get(conf, sid1);
var s2 = this.get(conf, sid2);
var n1 = s1 ? s1['.index'] : NaN;
var n2 = s2 ? s2['.index'] : NaN;
if (isNaN(n1) || isNaN(n2))
return false;
s1['.index'] = n2;
s2['.index'] = n1;
this.state.reorder[conf] = true;
return true;
},
save: function()
{
L.rpc.batch();
var v = this.state.values;
var n = this.state.creates;
var c = this.state.changes;
var d = this.state.deletes;
var self = this;
var snew = [ ];
var pkgs = { };
if (n)
for (var conf in n)
{
for (var sid in n[conf])
{
var r = {
config: conf,
values: { }
};
for (var k in n[conf][sid])
{
if (k == '.type')
r.type = n[conf][sid][k];
else if (k == '.create')
r.name = n[conf][sid][k];
else if (k.charAt(0) != '.')
r.values[k] = n[conf][sid][k];
}
snew.push(n[conf][sid]);
self.callAdd(r.config, r.type, r.name, r.values);
}
pkgs[conf] = true;
}
if (c)
for (var conf in c)
{
for (var sid in c[conf])
self.callSet(conf, sid, c[conf][sid]);
pkgs[conf] = true;
}
if (d)
for (var conf in d)
{
for (var sid in d[conf])
{
var o = d[conf][sid];
self.callDelete(conf, sid, (o === true) ? undefined : o);
}
pkgs[conf] = true;
}
return L.rpc.flush().then(function(responses) {
/*
array "snew" holds references to the created uci sections,
use it to assign the returned names of the new sections
*/
for (var i = 0; i < snew.length; i++)
snew[i]['.name'] = responses[i];
return self.reorderSections();
}).then(function() {
pkgs = L.toArray(pkgs);
self.unload(pkgs);
return self.load(pkgs);
});
},
apply: function(timeout)
{
var self = this;
var date = new Date();
var deferred = $.Deferred();
if (typeof(timeout) != 'number' || timeout < 1)
timeout = 10;
self.callApply(timeout, true).then(function(rv) {
if (rv != 0)
{
deferred.rejectWith(self, [ rv ]);
return;
}
var try_deadline = date.getTime() + 1000 * timeout;
var try_confirm = function()
{
return self.callConfirm().then(function(rv) {
if (rv != 0)
{
if (date.getTime() < try_deadline)
window.setTimeout(try_confirm, 250);
else
deferred.rejectWith(self, [ rv ]);
return;
}
deferred.resolveWith(self, [ rv ]);
});
};
window.setTimeout(try_confirm, 1000);
});
return deferred;
},
changes: L.rpc.declare({
object: 'uci',
method: 'changes',
expect: { changes: { } }
}),
readable: function(conf)
{
return L.session.hasACL('uci', conf, 'read');
},
writable: function(conf)
{
return L.session.hasACL('uci', conf, 'write');
}
});

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,90 @@
L.ui.view.extend({
title: L.tr('Diagnostics'),
runPing: L.rpc.declare({
object: 'luci2.network',
method: 'ping',
params: [ 'data' ],
expect: { '': { code: -1 } }
}),
runPing6: L.rpc.declare({
object: 'luci2.network',
method: 'ping6',
params: [ 'data' ],
expect: { '': { code: -1 } }
}),
runTraceroute: L.rpc.declare({
object: 'luci2.network',
method: 'traceroute',
params: [ 'data' ],
expect: { '': { code: -1 } }
}),
runTraceroute6: L.rpc.declare({
object: 'luci2.network',
method: 'traceroute6',
params: [ 'data' ],
expect: { '': { code: -1 } }
}),
runNslookup: L.rpc.declare({
object: 'luci2.network',
method: 'nslookup',
params: [ 'data' ],
expect: { '': { code: -1 } }
}),
execute: function() {
var self = this;
var tools = [ ];
$.when(
self.runPing('?').then(function(rv) {
if (rv.code != -1) tools.push(['runPing', L.tr('IPv4 Ping')]);
}),
self.runPing6('?').then(function(rv) {
if (rv.code != -1) tools.push(['runPing6', L.tr('IPv6 Ping')]);
}),
self.runTraceroute('?').then(function(rv) {
if (rv.code != -1) tools.push(['runTraceroute', L.tr('IPv4 Traceroute')]);
}),
self.runTraceroute6('?').then(function(rv) {
if (rv.code != -1) tools.push(['runTraceroute6', L.tr('IPv6 Tracroute')]);
}),
self.runNslookup('?').then(function(rv) {
if (rv.code != -1) tools.push(['runNslookup', L.tr('DNS Lookup')]);
})
).then(function() {
tools.sort(function(a, b) {
if (a[0] < b[0])
return -1;
else if (a[0] > b[0])
return 1;
else
return 0;
});
for (var i = 0; i < tools.length; i++)
$('#tool').append($('<option />').attr('value', tools[i][0]).text(tools[i][1]));
$('#tool').val('runPing');
$('#run').click(function() {
L.ui.loading(true);
self[$('#tool').val()]($('#host').val()).then(function(rv) {
$('#output').empty().show();
if (rv.stdout)
$('#output').text(rv.stdout);
if (rv.stderr)
$('#output').append($('<span />').css('color', 'red').text(rv.stderr));
L.ui.loading(false);
});
});
});
}
});

View file

@ -0,0 +1,29 @@
L.ui.view.extend({
title: L.tr('Hostnames'),
description: L.tr('Manage static host records to let the local DNS server resolve certain names to specific IP addresses.'),
execute: function() {
var m = new L.cbi.Map('dhcp', {
readonly: !this.options.acls.hostnames
});
var s = m.section(L.cbi.TableSection, 'domain', {
anonymous: true,
addremove: true,
add_caption: L.tr('Add new hostname'),
remove_caption: L.tr('Remove hostname')
});
s.option(L.cbi.InputValue, 'name', {
caption: L.tr('Hostname'),
datatype: 'hostname'
});
s.option(L.cbi.InputValue, 'ip', {
caption: L.tr('IP address'),
datatype: 'ipaddr'
});
return m.insertInto('#map');
}
});

View file

@ -0,0 +1,361 @@
L.ui.view.extend({
title: L.tr('Interface Overview'),
pendingRestart: [ ],
pendingShutdown: [ ],
setUp: L.rpc.declare({
object: 'luci2.network',
method: 'ifup',
params: [ 'data' ],
expect: { '': { code: -1 } }
}),
setDown: L.rpc.declare({
object: 'luci2.network',
method: 'ifdown',
params: [ 'data' ],
expect: { '': { code: -1 } }
}),
renderDeviceIcon: function(dev, up)
{
var icon = dev ? dev.icon(up) : L.globals.resource + '/icons/ethernet_disabled.png';
var desc = dev ? '%s (%s)'.format(dev.description(), dev.name()) : L.tr('Network interface not present');
return $('<img />')
.attr('title', desc)
.attr('src', icon);
},
renderNetworkBadge: function(network, div)
{
var dest = div || $('#network-badge-%s'.format(network.name()));
var device = network.getDevice(); //network.device || { type: 'Network device', device: '?' };
var subdevs = network.getSubdevices();
if (div)
{
var h = $('<div />')
.addClass('ifacebox-head')
.text(network.name());
if (network.zone)
h.css('background-color', network.zone.color).attr('title', L.trc('Interface status', 'Part of zone "%s"').format(network.zone.name));
else
h.css('background-color', '#cccccc').attr('title', L.trc('Interface status', 'Not part of any zone'));
dest.append(h);
}
else
{
dest.children('div.ifacebox-body').remove();
}
var b = $('<div />')
.addClass('ifacebox-body');
b.append(this.renderDeviceIcon(device, network.isUp()));
if (subdevs.length)
{
b.append('(');
for (var i = 0; i < subdevs.length; i++)
b.append(this.renderDeviceIcon(subdevs[i], subdevs[i].isUp()));
b.append(')');
}
b.append($('<br />')).append($('<small />').text(device ? device.name() : '?'));
return dest.append(b);
},
renderNetworkStatus: function(network, div)
{
var rv = '';
if (network.isUp())
{
rv += '<strong>%s</strong>: %t<br />'.format(
L.tr('Uptime'),
network.getUptime()
);
}
else
{
rv += '<strong>%s</strong>: %s<br />'.format(
L.tr('Uptime'),
L.tr('Interface is down')
);
}
var v4 = network.getIPv4Addrs();
if (v4.length)
rv += '<strong>%s</strong>: %s<br />'.format(
L.trc('Interface status', 'IPv4'),
v4.join(', ')
);
var v6 = network.getIPv6Addrs();
if (v6.length)
rv += '<strong>%s</strong>: %s<br />'.format(
L.trc('Interface status', 'IPv6'),
v6.join(', ')
);
return (div || $('#network-status-%s'.format(network.name())))
.empty()
.append(rv);
},
renderNetworkChart: function(network, div)
{
var dest = (div || $('#network-chart-%s'.format(network.name())));
dest.empty();
dest.append($('<div />')
.addClass('traffic-chart')
.append($('<span />')
.attr('id', 'network-chart-tx-%s'.format(network.name()))
.hide())
.append($('<label />')));
dest.append($('<div />')
.addClass('traffic-chart')
.append($('<span />')
.attr('id', 'network-chart-rx-%s'.format(network.name()))
.hide())
.append($('<label />')));
dest.append($('<small />')
.addClass('traffic-stats')
.text(L.tr('Loading statistics…')));
return dest;
},
refreshNetworkStatus: function()
{
var self = this;
var deferreds = [ ];
while (self.pendingRestart.length)
deferreds.push(self.setUp(self.pendingRestart.shift()));
while (self.pendingShutdown.length)
deferreds.push(self.setDown(self.pendingShutdown.shift()));
return $.when.apply($, deferreds).then(function() {
$('button').prop('disabled', false);
return $.when(
L.network.refreshDeviceStatus(),
L.network.refreshInterfaceStatus()
);
}).then(function() {
var networks = L.network.getInterfaces();
for (var i = 0; i < networks.length; i++)
{
self.renderNetworkBadge(networks[i]);
self.renderNetworkStatus(networks[i]);
}
var max = 0.1;
var networks = L.network.getInterfaces();
for (var i = 0; i < networks.length; i++)
{
var network = networks[i];
var history = network.getTrafficHistory();
var stats = network.getStatistics();
var tx = $('#network-chart-tx-%s'.format(network.name()));
var rx = $('#network-chart-rx-%s'.format(network.name()));
var tx_rate = history.tx_bytes[history.tx_bytes.length - 1];
var rx_rate = history.rx_bytes[history.rx_bytes.length - 1];
max = Math.max(Math.max.apply(Math, history.rx_bytes),
Math.max.apply(Math, history.tx_bytes),
max);
for (var j = 0; j < history.rx_bytes.length; j++)
history.rx_bytes[j] = -Math.abs(history.rx_bytes[j]);
tx.text(history.tx_bytes.join(','));
rx.text(history.rx_bytes.join(','));
tx.next().attr('title', '%.2mB/s'.format(tx_rate));
rx.next().attr('title', '%.2mB/s'.format(rx_rate));
tx.nextAll('label').html('↑ %.2mB/s'.format(tx_rate));
rx.nextAll('label').html('↓ %.2mB/s'.format(rx_rate));
tx.parent().nextAll('small.traffic-stats').html(
'<strong>%s</strong>: %.2mB (%d Pkts.)<br />'.format(
L.trc('Interface status', 'TX'),
stats.tx_bytes, stats.tx_packets) +
'<strong>%s</strong>: %.2mB (%d Pkts.)<br />'.format(
L.trc('Interface status', 'RX'),
stats.rx_bytes, stats.rx_packets));
}
for (var i = 0; i < networks.length; i++)
{
var network = networks[i];
var tx = $('#network-chart-tx-%s'.format(network.name()));
var rx = $('#network-chart-rx-%s'.format(network.name()));
tx.peity('line', { width: 200, min: 0, max: max });
rx.peity('line', { width: 200, min: -max, max: 0 });
}
L.ui.loading(false);
});
},
renderContents: function(networks)
{
var self = this;
var list = new L.ui.table({
columns: [ {
caption: L.tr('Network'),
width: '120px',
format: function(v) {
var div = $('<div />')
.attr('id', 'network-badge-%s'.format(v.name()))
.addClass('ifacebox');
return self.renderNetworkBadge(v, div);
}
}, {
caption: L.tr('Traffic'),
width: '215px',
format: function(v) {
var div = $('<div />').attr('id', 'network-chart-%s'.format(v.name()));
return self.renderNetworkChart(v, div);
}
}, {
caption: L.tr('Status'),
format: function(v) {
var div = $('<small />').attr('id', 'network-status-%s'.format(v.name()));
return self.renderNetworkStatus(v, div);
}
}, {
caption: L.tr('Actions'),
format: function(v, n) {
return $('<div />')
.addClass('btn-group btn-group-sm')
.append(L.ui.button(L.tr('Restart'), 'default', L.tr('Enable or restart interface'))
.click({ self: self, network: v }, self.handleIfup))
.append(L.ui.button(L.tr('Shutdown'), 'default', L.tr('Shut down interface'))
.click({ self: self, network: v }, self.handleIfdown))
.append(L.ui.button(L.tr('Edit'), 'primary', L.tr('Edit interface'))
.click({ self: self, network: v }, self.handleEdit))
.append(L.ui.button(L.tr('Delete'), 'danger', L.tr('Delete interface'))
.click({ self: self, network: v }, self.handleRemove));
}
} ]
});
for (var i = 0; i < networks.length; i++)
list.row([ networks[i], networks[i], networks[i], networks[i] ]);
self.repeat(self.refreshNetworkStatus, 5000);
$('#map')
.append(list.render());
},
renderInterfaceForm: function(network)
{
var m = new L.cbi.Map('network', {
tabbed: true,
caption: 'Interface config',
description: 'I can config interface!!!!'
});
var s4 = m.section(L.cbi.TypedSection, 'route', {
caption: L.tr('Static IPv4 Routes'),
anonymous: true,
addremove: true,
sortable: true,
add_caption: L.tr('Add new route'),
remove_caption: L.tr('Remove route')
});
var ifc = s4.option(L.cbi.ListValue, 'interface', {
caption: L.tr('Interface')
});
ifc.value('foo');
s4.option(L.cbi.InputValue, 'target', {
caption: L.tr('Target'),
datatype: 'ip4addr'
});
s4.option(L.cbi.InputValue, 'netmask', {
caption: L.tr('IPv4-Netmask'),
datatype: 'ip4addr',
placeholder: '255.255.255.255',
optional: true
});
s4.option(L.cbi.InputValue, 'gateway', {
caption: L.tr('IPv4-Gateway'),
datatype: 'ip4addr',
optional: true
});
s4.option(L.cbi.InputValue, 'metric', {
caption: L.tr('Metric'),
datatype: 'range(0,255)',
placeholder: 0,
optional: true
});
s4.option(L.cbi.InputValue, 'mtu', {
caption: L.tr('MTU'),
datatype: 'range(64,9000)',
placeholder: 1500,
optional: true
});
return m;
},
handleIfup: function(ev) {
this.disabled = true;
this.blur();
ev.data.self.pendingRestart.push(ev.data.network['interface']);
},
handleIfdown: function(ev) {
this.disabled = true;
this.blur();
ev.data.self.pendingShutdown.push(ev.data.network['interface']);
},
handleEdit: function(ev) {
var self = ev.data.self;
var network = ev.data.network;
return network.createForm(L.cbi.Modal).show();
},
execute: function() {
var self = this;
return L.network.load().then(function() {
self.renderContents(L.network.getInterfaces());
});
}
});

View file

@ -0,0 +1,110 @@
L.ui.view.extend({
title: L.tr('Routes'),
description: L.tr('Routes specify over which interface and gateway a certain host or network can be reached.'),
execute: function() {
var self = this;
var ifaces = L.network.getInterfaces();
var m = new L.cbi.Map('network', {
readonly: !self.options.acls.network
});
var s4 = m.section(L.cbi.GridSection, 'route', {
caption: L.tr('Static IPv4 Routes'),
anonymous: true,
addremove: true,
sortable: true,
add_caption: L.tr('Add new route'),
remove_caption: L.tr('Remove route')
});
var ifc = s4.option(L.cbi.ListValue, 'interface', {
caption: L.tr('Interface')
});
for (var i = 0; i < ifaces.length; i++)
ifc.value(ifaces[i].name());
s4.option(L.cbi.InputValue, 'target', {
caption: L.tr('Target'),
datatype: 'ip4addr',
width: 2
});
s4.option(L.cbi.InputValue, 'netmask', {
caption: L.tr('IPv4-Netmask'),
datatype: 'ip4addr',
placeholder: '255.255.255.255',
optional: true,
width: 2
});
s4.option(L.cbi.InputValue, 'gateway', {
caption: L.tr('IPv4-Gateway'),
datatype: 'ip4addr',
optional: true,
width: 2
});
s4.option(L.cbi.InputValue, 'metric', {
caption: L.tr('Metric'),
datatype: 'range(0,255)',
placeholder: 0,
optional: true
});
s4.option(L.cbi.InputValue, 'mtu', {
caption: L.tr('MTU'),
datatype: 'range(64,9000)',
placeholder: 1500,
optional: true
});
var s6 = m.section(L.cbi.GridSection, 'route6', {
caption: L.tr('Static IPv6 Routes'),
anonymous: true,
addremove: true,
sortable: true,
add_caption: L.tr('Add new route'),
remove_caption: L.tr('Remove route')
});
var ifc = s6.option(L.cbi.ListValue, 'interface', {
caption: L.tr('Interface')
});
for (var i = 0; i < ifaces.length; i++)
ifc.value(ifaces[i].name());
s6.option(L.cbi.InputValue, 'target', {
caption: L.tr('Target'),
datatype: 'ip6addr',
width: 3
});
s6.option(L.cbi.InputValue, 'gateway', {
caption: L.tr('IPv6-Gateway'),
datatype: 'ip6addr',
optional: true,
width: 3
});
s6.option(L.cbi.InputValue, 'metric', {
caption: L.tr('Metric'),
datatype: 'range(0,255)',
placeholder: 0,
optional: true
});
s6.option(L.cbi.InputValue, 'mtu', {
caption: L.tr('MTU'),
datatype: 'range(64,9000)',
placeholder: 1500,
optional: true
});
m.insertInto('#map');
}
});

View file

@ -0,0 +1,338 @@
L.ui.view.extend({
title: L.tr('Switch'),
description: L.tr('The network ports on this device can be combined to several VLANs in which computers can communicate directly with each other. VLANs are often used to separate different network segments. Often there is by default one Uplink port for a connection to the next greater network like the internet and other ports for a local network.'),
listSwitchNames: L.rpc.declare({
object: 'luci2.network',
method: 'switch_list',
expect: { switches: [ ] }
}),
getSwitchInfo: L.rpc.declare({
object: 'luci2.network',
method: 'switch_info',
params: [ 'switch' ],
expect: { info: { } },
filter: function(data, params) {
data['attrs'] = data['switch'];
data['vlan_attrs'] = data['vlan'];
data['port_attrs'] = data['port'];
data['switch'] = params['switch'];
delete data.vlan;
delete data.port;
return data;
}
}),
getSwitchStatus: L.rpc.declare({
object: 'luci2.network',
method: 'switch_status',
params: [ 'switch' ],
expect: { ports: [ ] }
}),
switchPortState: L.cbi.ListValue.extend({
choices: [
[ 'n', L.trc('Switch port state', 'off') ],
[ 'u', L.trc('Switch port state', 'untagged') ],
[ 't', L.trc('Switch port state', 'tagged') ]
],
init: function(name, options)
{
var self = this;
options.datatype = function(val, elem)
{
if (val == 'u')
{
var u = false;
var sections = self.ownerSection.getUCISections();
for (var i = 0; i < sections.length; i++)
{
var v = self.formvalue(sections[i]['.name']);
if (v == 'u')
{
if (u)
return L.tr('Port must not be untagged in multiple VLANs');
u = true;
}
}
}
return true;
};
this.callSuper('init', name, options);
},
ucivalue: function(sid)
{
var ports = (this.ownerMap.get('network', sid, 'ports') || '').match(/[0-9]+[tu]?/g);
if (ports)
for (var i = 0; i < ports.length; i++)
if (ports[i].match(/^([0-9]+)([tu]?)$/))
if (RegExp.$1 == this.name)
return RegExp.$2 || 'u';
return 'n';
},
save: function(sid)
{
return;
}
}),
execute: function() {
var self = this;
return self.listSwitchNames().then(function(switches) {
L.rpc.batch();
for (var i = 0; i < switches.length; i++)
self.getSwitchInfo(switches[i]);
return L.rpc.flush();
}).then(function(switches) {
var m = new L.cbi.Map('network', {
readonly: !self.options.acls.network
});
for (var i = 0; i < switches.length; i++)
{
var swname = switches[i]['switch'];
var vid_opt = 'vlan';
var v4k_opt = undefined;
var pvid_opt = undefined;
var max_vid = switches[i].num_vlans - 1;
var num_vlans = switches[i].num_vlans;
for (var j = 0; j < switches[i].vlan_attrs.length; j++)
{
switch (switches[i].vlan_attrs[j].name)
{
case 'tag':
case 'vid':
case 'pvid':
vid_opt = switches[i].vlan_attrs[j].name;
max_vid = 4095;
break;
}
}
for (var j = 0; j < switches[i].port_attrs.length; j++)
{
switch (switches[i].port_attrs[j].name)
{
case 'pvid':
pvid_opt = switches[i].port_attrs[j].name;
break;
}
}
var sw = m.section(L.cbi.TypedSection, 'switch', {
caption: L.tr('Switch "%s"').format(switches[i].model),
swname: swname
});
sw.filter = function(section) {
return (section['.name'] == this.options.swname ||
section.name == this.options.swname);
};
for (var j = 0; j < switches[i].attrs.length; j++)
{
switch (switches[i].attrs[j].name)
{
case 'enable_vlan':
sw.option(L.cbi.CheckboxValue, 'enable_vlan', {
caption: L.tr('Enable VLAN functionality')
});
break;
case 'enable_learning':
sw.option(L.cbi.CheckboxValue, 'enable_learning', {
caption: L.tr('Enable learning and aging'),
initial: true,
optional: true
});
break;
case 'max_length':
sw.option(L.cbi.CheckboxValue, 'max_length', {
caption: L.tr('Enable Jumbo Frame passthrough'),
enabled: '3',
optional: true
});
break;
case 'enable_vlan4k':
v4k_opt = switches[i].attrs[j].name;
break;
}
}
var vlans = m.section(L.cbi.TableSection, 'switch_vlan', {
caption: L.tr('VLANs on "%s"').format(switches[i].model),
swname: swname,
addremove: true,
add_caption: L.tr('Add VLAN entry …')
});
vlans.add = function() {
var sections = this.getUCISections();
var used_vids = { };
for (var j = 0; j < sections.length; j++)
{
var v = this.ownerMap.get('network', sections[j]['.name'], 'vlan');
if (v)
used_vids[v] = true;
}
for (var j = 1; j < num_vlans; j++)
{
if (used_vids[j.toString()])
continue;
var sid = this.ownerMap.add('network', 'switch_vlan');
this.ownerMap.set('network', sid, 'device', this.options.swname);
this.ownerMap.set('network', sid, 'vlan', j);
break;
}
};
vlans.filter = function(section) {
return (section.device == this.options.swname);
};
vlans.sections = function() {
var s = this.callSuper('sections');
s.sort(function(a, b) {
var x = parseInt(a[vid_opt] || a.vlan);
if (isNaN(x))
x = 9999;
var y = parseInt(b[vid_opt] || b.vlan);
if (isNaN(y))
y = 9999;
return (x - y);
});
return s;
};
var port_opts = [ ];
var vo = vlans.option(L.cbi.InputValue, vid_opt, {
caption: L.tr('VLAN ID'),
datatype: function(val) {
var sections = vlans.getUCISections();
var used_vids = { };
for (var j = 0; j < sections.length; j++)
{
var v = vlans.fields[vid_opt].formvalue(sections[j]['.name']);
if (!v)
continue;
if (used_vids[v])
return L.tr('VLAN ID must be unique');
used_vids[v] = true;
}
if (val.match(/[^0-9]/))
return L.tr('Invalid VLAN ID');
val = parseInt(val, 10);
if (val < 1 || val > max_vid)
return L.tr('VLAN ID must be a value between %u and %u').format(1, max_vid);
return true;
}
});
vo.ucivalue = function(sid) {
var id = this.ownerMap.get('network', sid, vid_opt);
if (isNaN(parseInt(id)))
id = this.ownerMap.get('network', sid, 'vlan');
return id;
};
vo.save = function(sid) {
var old_ports = this.ownerMap.get('network', sid, 'ports');
var new_ports = '';
for (var j = 0; j < port_opts.length; j++)
{
var v = port_opts[j].formvalue(sid);
if (v != 'n')
new_ports += '%s%d%s'.format(
new_ports ? ' ' : '', j,
(v == 'u') ? '' : 't');
}
if (new_ports != old_ports)
this.ownerMap.set('network', sid, 'ports', new_ports);
if (v4k_opt)
{
var s = sw.getUCISections();
for (var j = 0; j < s.length; j++)
this.ownerMap.set('network', s[j]['.name'], v4k_opt, '1');
}
this.callSuper('save', sid);
};
for (var j = 0; j < switches[i].num_ports; j++)
{
var label = L.trc('Switch port label', 'Port %d').format(j);
if (j == switches[i].cpu_port)
label = L.trc('Switch port label', 'CPU');
var po = vlans.option(self.switchPortState, j.toString(), {
caption: label + '<br /><small id="portstatus-%s-%d"></small>'.format(swname, j)
});
port_opts.push(po);
}
}
return m.insertInto('#map').then(function() {
self.repeat(function() {
return self.getSwitchStatus(swname).then(function(ports) {
for (var j = 0; j < ports.length; j++)
{
var s = L.tr('No link');
var d = '&#160;';
if (ports[j].link)
{
s = '%dbaseT'.format(ports[j].speed);
d = ports[j].full_duplex ? L.tr('Full-duplex') : L.tr('Half-duplex');
}
$('#portstatus-%s-%d'.format(swname, j))
.empty().append(s + '<br />' + d);
}
});
}, 5000);
});
});
}
});

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