for (var i = 0, color = '#';
i < 3;
- color += ('00' + ((hash >> i++ * 8) & 0xFF).tozoneing(16)).slice(-2));
+ color += ('00' + ((hash >> i++ * 8) & 0xFF).tostring(16)).slice(-2));
return color;
},
var state = _luci2.ui._loading || (_luci2.ui._loading = {
modal: $('<div />')
+ .css('z-index', 2000)
.addClass('modal fade')
.append($('<div />')
.addClass('modal-dialog')
}
}),
+ _render_change_indicator: function()
+ {
+ return $('<ul />')
+ .addClass('nav navbar-nav navbar-right')
+ .append($('<li />')
+ .append($('<a />')
+ .attr('id', 'changes')
+ .attr('href', '#')
+ .append($('<span />')
+ .addClass('label label-info'))));
+ },
+
renderMainMenu: _luci2.rpc.declare({
object: 'luci2.ui',
method: 'menu',
$('#mainmenu')
.empty()
- .append(_luci2.globals.mainMenu.render(0, 1));
+ .append(_luci2.globals.mainMenu.render(0, 1))
+ .append(_luci2.ui._render_change_indicator());
}
}),
renderView: function()
{
- var node = arguments[0];
- var name = node.view.split(/\//).join('.');
- var args = [ ];
+ var node = arguments[0];
+ var name = node.view.split(/\//).join('.');
+ var cname = _luci2.toClassName(name);
+ var views = _luci2.views || (_luci2.views = { });
+ var args = [ ];
for (var i = 1; i < arguments.length; i++)
args.push(arguments[i]);
_luci2.globals.currentView.finish();
_luci2.ui.renderViewMenu();
-
- if (!_luci2._views)
- _luci2._views = { };
-
_luci2.setHash('view', node.view);
- if (_luci2._views[name] instanceof _luci2.ui.view)
+ if (views[cname] instanceof _luci2.ui.view)
{
- _luci2.globals.currentView = _luci2._views[name];
- return _luci2._views[name].render.apply(_luci2._views[name], args);
+ _luci2.globals.currentView = views[cname];
+ return views[cname].render.apply(views[cname], args);
}
var url = _luci2.globals.resource + '/view/' + name + '.js';
var viewConstructor = eval(viewConstructorSource);
- _luci2._views[name] = new viewConstructor({
+ views[cname] = new viewConstructor({
name: name,
acls: node.write || { }
});
- _luci2.globals.currentView = _luci2._views[name];
- return _luci2._views[name].render.apply(_luci2._views[name], args);
+ _luci2.globals.currentView = views[cname];
+ return views[cname].render.apply(views[cname], args);
}
catch(e) {
alert('Unable to instantiate view "%s": %s'.format(url, e));
});
},
+ changeView: function()
+ {
+ var name = _luci2.getHash('view');
+ var node = _luci2.globals.defaultNode;
+
+ if (name && _luci2.globals.mainMenu)
+ node = _luci2.globals.mainMenu.getNode(name);
+
+ if (node)
+ {
+ _luci2.ui.loading(true);
+ _luci2.ui.renderView(node).then(function() {
+ _luci2.ui.loading(false);
+ });
+ }
+ },
+
updateHostname: function()
{
return _luci2.system.getBoardInfo().then(function(info) {
switch (c[0])
{
case 'order':
+ log.push('uci reorder %s.<ins>%s=<strong>%s</strong></ins>'.format(config, c[1], c[2]));
break;
case 'remove':
if (n > 0)
$('#changes')
- .empty()
- .show()
- .append($('<a />')
- .attr('href', '#')
- .addClass('label')
- .addClass('notice')
- .text(_luci2.trcp('Pending configuration changes', '1 change', '%d changes', n).format(n))
- .click(function(ev) {
- _luci2.ui.dialog(_luci2.tr('Staged configuration changes'), html, { style: 'close' });
- ev.preventDefault();
- }));
+ .click(function(ev) {
+ _luci2.ui.dialog(_luci2.tr('Staged configuration changes'), html, {
+ style: 'confirm',
+ confirm: function() {
+ _luci2.uci.apply().then(
+ function(code) { alert('Success with code ' + code); },
+ function(code) { alert('Error with code ' + code); }
+ );
+ }
+ });
+ ev.preventDefault();
+ })
+ .children('span')
+ .show()
+ .text(_luci2.trcp('Pending configuration changes', '1 change', '%d changes', n).format(n));
else
- $('#changes')
- .hide();
+ $('#changes').children('span').hide();
});
},
_luci2.ui.loading(true);
$.when(
+ _luci2.session.updateACLs(),
_luci2.ui.updateHostname(),
_luci2.ui.updateChanges(),
- _luci2.ui.renderMainMenu()
+ _luci2.ui.renderMainMenu(),
+ _luci2.NetworkModel.init()
).then(function() {
_luci2.ui.renderView(_luci2.globals.defaultNode).then(function() {
_luci2.ui.loading(false);
- })
+ });
+
+ $(window).on('hashchange', function() {
+ _luci2.ui.changeView();
+ });
});
},
_onclick: function(ev)
{
- _luci2.ui.loading(true);
- _luci2.ui.renderView(ev.data).then(function() {
- _luci2.ui.loading(false);
- });
+ _luci2.setHash('view', ev.data);
ev.preventDefault();
this.blur();
}
else
{
- item.find('a').click(nodes[i], this._onclick);
+ item.find('a').click(nodes[i].view, this._onclick);
}
}
this.instance = { };
this.dependencies = [ ];
this.rdependency = { };
+ this.events = { };
this.options = _luci2.defaults(options, {
placeholder: '',
return i.top;
},
+ active: function(sid)
+ {
+ return (this.instance[sid] && !this.instance[sid].disabled);
+ },
+
ucipath: function(sid)
{
return {
}
if (rv)
+ {
for (var field in d.self.rdependency)
d.self.rdependency[field].toggle(d.sid);
+ d.self.section.tabtoggle(d.sid);
+ }
+
return rv;
},
validator: function(sid, elem, multi)
{
+ var evdata = {
+ self: this,
+ sid: sid,
+ elem: elem,
+ multi: multi,
+ inst: this.instance[sid],
+ opt: this.options.optional
+ };
+
+ for (var evname in this.events)
+ elem.on(evname, evdata, this.events[evname]);
+
if (typeof(this.options.datatype) == 'undefined' && $.isEmptyObject(this.rdependency))
return elem;
if (typeof(this.options.datatype) == 'string')
{
try {
- vstack = _luci2.cbi.validation.compile(this.options.datatype);
+ evdata.vstack = _luci2.cbi.validation.compile(this.options.datatype);
} catch(e) { };
}
else if (typeof(this.options.datatype) == 'function')
{
var vfunc = this.options.datatype;
- vstack = [ function(elem) {
+ evdata.vstack = [ function(elem) {
var rv = vfunc(this, elem);
if (rv !== true)
validation.message = rv;
}, [ elem ] ];
}
- var evdata = {
- self: this,
- sid: sid,
- elem: elem,
- multi: multi,
- vstack: vstack,
- inst: this.instance[sid],
- opt: this.options.optional
- };
-
if (elem.prop('tagName') == 'SELECT')
{
elem.change(evdata, this._ev_validate);
return (i.disabled || i.error.text() == '');
},
- depends: function(d, v)
+ depends: function(d, v, add)
{
var dep;
if ($.isEmptyObject(dep))
return this;
- this.dependencies.push(dep);
+ if (!add || !this.dependencies.length)
+ this.dependencies.push(dep);
+ else
+ for (var i = 0; i < this.dependencies.length; i++)
+ $.extend(this.dependencies[i], dep);
return this;
},
break;
}
}
- else if (typeof(cmp) == 'string')
+ else if (typeof(cmp) == 'string' || typeof(cmp) == 'number')
{
if (val != cmp)
{
}
return false;
+ },
+
+ on: function(evname, evfunc)
+ {
+ this.events[evname] = evfunc;
+ return this;
}
});
var s = $('<select />')
.addClass('form-control');
- if (this.options.optional)
+ if (this.options.optional && !this.has_empty)
$('<option />')
.attr('value', '')
.text(_luci2.tr('-- Please choose --'))
if (!this.choices)
this.choices = [ ];
+ if (k == '')
+ this.has_empty = true;
+
this.choices.push([k, v || k]);
return this;
}
{
ev.data.select.hide();
ev.data.input.show().focus();
-
- var v = ev.data.input.val();
- ev.data.input.val(' ');
- ev.data.input.val(v);
+ ev.data.input.val('');
}
else if (self.options.optional && s.selectedIndex == 0)
{
{
ev.data.input.val(ev.data.select.val());
}
+
+ ev.stopPropagation();
},
_blur: function(ev)
ev.data.select.empty();
- if (self.options.optional)
+ if (self.options.optional && !self.has_empty)
$('<option />')
.attr('value', '')
.text(_luci2.tr('-- please choose --'))
.appendTo(ev.data.select);
ev.data.input.hide();
- ev.data.select.val(val).show().focus();
+ ev.data.select.val(val).show().blur();
},
_enter: function(ev)
.attr('id', this.id(sid));
var t = $('<input />')
+ .addClass('form-control')
.attr('type', 'text')
.hide()
.appendTo(d);
var s = $('<select />')
+ .addClass('form-control')
.appendTo(d);
var evdata = {
self: this,
- input: this.validator(sid, t),
- select: this.validator(sid, s)
+ input: t,
+ select: s
};
s.change(evdata, this._change);
t.val(this.ucivalue(sid));
t.blur();
+ this.validator(sid, t);
+ this.validator(sid, s);
+
return d;
},
if (!this.choices)
this.choices = [ ];
+ if (k == '')
+ this.has_empty = true;
+
this.choices.push([k, v || k]);
return this;
},
formvalue: function(sid)
{
var rv = [ ];
- var fields = $('#' + this.id(sid) + ' > input');
+ var fields = $('#' + this.id(sid) + ' input');
for (var i = 0; i < fields.length; i++)
if (typeof(fields[i].value) == 'string' && fields[i].value.length)
}
});
- this.cbi.NetworkList = this.cbi.AbstractValue.extend({
- load: function(sid)
+ this.cbi.ButtonValue = this.cbi.AbstractValue.extend({
+ widget: function(sid)
{
- var self = this;
+ this.options.optional = true;
- if (!self.interfaces)
- {
- self.interfaces = [ ];
- return _luci2.network.getNetworkStatus().then(function(ifaces) {
- self.interfaces = ifaces;
- self = null;
- });
- }
+ var btn = $('<button />')
+ .addClass('btn btn-default')
+ .attr('id', this.id(sid))
+ .attr('type', 'button')
+ .text(this.label('text'));
- return undefined;
+ return this.validator(sid, btn);
+ }
+ });
+
+ this.cbi.NetworkList = this.cbi.AbstractValue.extend({
+ load: function(sid)
+ {
+ return _luci2.NetworkModel.init();
},
_device_icon: function(dev)
{
- var type = 'ethernet';
- var desc = _luci2.tr('Ethernet device');
-
- if (dev.type == 'IP tunnel')
- {
- type = 'tunnel';
- desc = _luci2.tr('Tunnel interface');
- }
- else if (dev['bridge-members'])
- {
- type = 'bridge';
- desc = _luci2.tr('Bridge');
- }
- else if (dev.wireless)
- {
- type = 'wifi';
- desc = _luci2.tr('Wireless Network');
- }
- else if (dev.device.indexOf('.') > 0)
- {
- type = 'vlan';
- desc = _luci2.tr('VLAN interface');
- }
-
return $('<img />')
- .attr('src', _luci2.globals.resource + '/icons/' + type + (dev.up ? '' : '_disabled') + '.png')
- .attr('title', '%s (%s)'.format(desc, dev.device));
+ .attr('src', dev.icon())
+ .attr('title', '%s (%s)'.format(dev.description(), dev.name() || '?'));
},
widget: function(sid)
for (var i = 0; i < value.length; i++)
check[value[i]] = true;
- if (this.interfaces)
+ var interfaces = _luci2.NetworkModel.getInterfaces();
+
+ for (var i = 0; i < interfaces.length; i++)
{
- for (var i = 0; i < this.interfaces.length; i++)
- {
- var iface = this.interfaces[i];
- var badge = $('<span />')
- .addClass('badge')
- .text('%s: '.format(iface['interface']));
-
- if (iface.device && iface.device.subdevices)
- for (var j = 0; j < iface.device.subdevices.length; j++)
- badge.append(this._device_icon(iface.device.subdevices[j]));
- else if (iface.device)
- badge.append(this._device_icon(iface.device));
- else
- badge.append($('<em />').text(_luci2.tr('(No devices attached)')));
+ var iface = interfaces[i];
+ var badge = $('<span />')
+ .addClass('badge')
+ .text('%s: '.format(iface.name()));
- $('<li />')
- .append($('<label />')
- .addClass(itype + ' inline')
- .append($('<input />')
- .attr('name', itype + id)
- .attr('type', itype)
- .attr('value', iface['interface'])
- .prop('checked', !!check[iface['interface']]))
- .append(badge))
- .appendTo(ul);
- }
+ var dev = iface.getDevice();
+ var subdevs = iface.getSubdevices();
+
+ if (subdevs.length)
+ for (var j = 0; j < subdevs.length; j++)
+ badge.append(this._device_icon(subdevs[j]));
+ else if (dev)
+ badge.append(this._device_icon(dev));
+ else
+ badge.append($('<em />').text(_luci2.tr('(No devices attached)')));
+
+ $('<li />')
+ .append($('<label />')
+ .addClass(itype + ' inline')
+ .append($('<input />')
+ .attr('name', itype + id)
+ .attr('type', itype)
+ .attr('value', iface.name())
+ .prop('checked', !!check[iface.name()]))
+ .append(badge))
+ .appendTo(ul);
}
if (!this.options.multiple)
.attr('name', itype + id)
.attr('type', itype)
.attr('value', '')
- .prop('checked', !value))
+ .prop('checked', $.isEmptyObject(check)))
.append(_luci2.tr('unspecified')))
.appendTo(ul);
}
return w;
},
+ tabtoggle: function(sid)
+ {
+ for (var i = 0; i < this.tabs.length; i++)
+ {
+ var tab = this.tabs[i];
+ var elem = $('#' + this.id('nodetab', sid, tab.id));
+ var empty = true;
+
+ for (var j = 0; j < tab.fields.length; j++)
+ {
+ if (tab.fields[j].active(sid))
+ {
+ empty = false;
+ break;
+ }
+ }
+
+ if (empty && elem.is(':visible'))
+ elem.fadeOut();
+ else if (!empty)
+ elem.fadeIn();
+ }
+ },
+
ucipackages: function(pkg)
{
for (var i = 0; i < this.tabs.length; i++)
validate: function()
{
- this.error_count = 0;
-
+ var errors = 0;
var as = this.sections();
for (var i = 0; i < as.length; i++)
var invals = this.validate_section(as[i]['.name']);
if (invals > 0)
- this.error_count += invals;
+ errors += invals;
}
var badge = $('#' + this.id('sectiontab')).children('span:first');
- if (this.error_count > 0)
+ if (errors > 0)
badge.show()
- .text(this.error_count)
- .attr('title', _luci2.trp('1 Error', '%d Errors', this.error_count).format(this.error_count));
+ .text(errors)
+ .attr('title', _luci2.trp('1 Error', '%d Errors', errors).format(errors));
else
badge.hide();
- return (this.error_count == 0);
+ return (errors == 0);
}
});
this.options = options;
this.tabs = [ ];
this.fields = { };
- this.error_count = 0;
this.active_panel = 0;
this.active_tab = { };
},
if (typeof(cb) == 'function')
for (var i = 0; i < s2.length; i++)
- cb.apply(this, [ s2[i] ]);
+ cb.call(this, s2[i]);
return s2;
},
}
});
+ this.cbi.SingleSection = this.cbi.NamedSection.extend({
+ render: function()
+ {
+ this.instance = { };
+ this.instance[this.uci_type] = { tabs: [ ] };
+
+ return this._render_section_body(this.uci_type, 0);
+ }
+ });
+
this.cbi.DummySection = this.cbi.TypedSection.extend({
sections: function(cb)
{
return body;
},
+ _render_footer: function()
+ {
+ return $('<div />')
+ .addClass('panel panel-default panel-body text-right')
+ .append($('<div />')
+ .addClass('btn-group')
+ .append(_luci2.ui.button(_luci2.tr('Save & Apply'), 'primary')
+ .click({ self: this }, function(ev) { }))
+ .append(_luci2.ui.button(_luci2.tr('Save'), 'default')
+ .click({ self: this }, function(ev) { ev.data.self.send(); }))
+ .append(_luci2.ui.button(_luci2.tr('Reset'), 'default')
+ .click({ self: this }, function(ev) { ev.data.self.insertInto(ev.data.self.target); })));
+ },
+
render: function()
{
var map = $('<form />');
map.append(this._render_body());
if (this.options.pageaction !== false)
- {
- map.append($('<div />')
- .addClass('panel panel-default panel-body text-right')
- .append($('<div />')
- .addClass('btn-group')
- .append(_luci2.ui.button(_luci2.tr('Save & Apply'), 'primary')
- .click({ self: this }, function(ev) { }))
- .append(_luci2.ui.button(_luci2.tr('Save'), 'default')
- .click({ self: this }, function(ev) { ev.data.self.send(); }))
- .append(_luci2.ui.button(_luci2.tr('Reset'), 'default')
- .click({ self: this }, function(ev) { ev.data.self.insertInto(ev.data.self.target); }))));
- }
+ map.append(this._render_footer());
return map;
},
});
}
});
+
+ this.cbi.Modal = this.cbi.Map.extend({
+ _render_footer: function()
+ {
+ return $('<div />')
+ .addClass('btn-group')
+ .append(_luci2.ui.button(_luci2.tr('Save & Apply'), 'primary')
+ .click({ self: this }, function(ev) { }))
+ .append(_luci2.ui.button(_luci2.tr('Save'), 'default')
+ .click({ self: this }, function(ev) { ev.data.self.send(); }))
+ .append(_luci2.ui.button(_luci2.tr('Cancel'), 'default')
+ .click({ self: this }, function(ev) { _luci2.ui.dialog(false); }));
+ },
+
+ render: function()
+ {
+ var modal = _luci2.ui.dialog(this.label('caption'), null, { wide: true });
+ var map = $('<form />');
+
+ var desc = this.label('description');
+ if (desc)
+ map.append($('<p />').text(desc));
+
+ map.append(this._render_body());
+
+ modal.find('.modal-body').append(map);
+ modal.find('.modal-footer').append(this._render_footer());
+
+ return modal;
+ },
+
+ redraw: function()
+ {
+ this.render();
+ this.finish();
+ },
+
+ show: function()
+ {
+ var self = this;
+
+ _luci2.ui.loading(true);
+
+ return self.load().then(function() {
+ self.render();
+ self.finish();
+
+ _luci2.ui.loading(false);
+ });
+ }
+ });
};