2015-06-24 19:44:31 +00:00
|
|
|
# Copyright 2015 Canonical, Ltd.
|
|
|
|
#
|
|
|
|
# This program is free software: you can redistribute it and/or modify
|
|
|
|
# it under the terms of the GNU Affero General Public License as
|
|
|
|
# published by the Free Software Foundation, either version 3 of the
|
|
|
|
# License, or (at your option) any later version.
|
|
|
|
#
|
|
|
|
# This program is distributed in the hope that it will be useful,
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
# GNU Affero General Public License for more details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU Affero General Public License
|
|
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
2015-07-21 15:55:02 +00:00
|
|
|
""" Network Model
|
|
|
|
|
|
|
|
Provides network device listings and extended network information
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
2015-06-24 19:44:31 +00:00
|
|
|
import logging
|
2015-11-06 15:19:44 +00:00
|
|
|
import textwrap
|
2016-08-22 04:46:56 +00:00
|
|
|
|
2017-02-03 01:20:05 +00:00
|
|
|
from urwid import (
|
|
|
|
LineBox,
|
|
|
|
ProgressBar,
|
|
|
|
Text,
|
|
|
|
WidgetWrap,
|
|
|
|
)
|
2016-08-22 04:46:56 +00:00
|
|
|
|
2016-06-30 18:17:01 +00:00
|
|
|
from subiquitycore.ui.buttons import cancel_btn, menu_btn, done_btn
|
2017-02-03 01:20:05 +00:00
|
|
|
from subiquitycore.ui.container import Columns, ListBox, Pile
|
2016-06-30 18:17:01 +00:00
|
|
|
from subiquitycore.ui.utils import Padding, Color
|
2016-07-26 02:21:24 +00:00
|
|
|
from subiquitycore.view import BaseView
|
2015-06-24 19:44:31 +00:00
|
|
|
|
|
|
|
|
2016-06-30 18:17:01 +00:00
|
|
|
log = logging.getLogger('subiquitycore.views.network')
|
2015-07-21 15:55:02 +00:00
|
|
|
|
|
|
|
|
2016-08-22 04:46:56 +00:00
|
|
|
class ApplyingConfigWidget(WidgetWrap):
|
|
|
|
|
|
|
|
def __init__(self, step_count, cancel_func):
|
|
|
|
self.cancel_func = cancel_func
|
|
|
|
button = cancel_btn(on_press=self.do_cancel)
|
|
|
|
self.bar = ProgressBar(normal='progress_incomplete',
|
|
|
|
complete='progress_complete',
|
|
|
|
current=0, done=step_count)
|
|
|
|
box = LineBox(Pile([self.bar,
|
|
|
|
Padding.fixed_10(button)]),
|
|
|
|
title="Applying network config")
|
|
|
|
super().__init__(box)
|
|
|
|
|
|
|
|
def advance(self):
|
|
|
|
self.bar.current += 1
|
|
|
|
|
|
|
|
def do_cancel(self, sender):
|
|
|
|
self.cancel_func()
|
|
|
|
|
2016-11-07 00:35:42 +00:00
|
|
|
def _build_wifi_info(dev):
|
|
|
|
r = []
|
|
|
|
if dev.actual_ssid is not None:
|
|
|
|
if dev.configured_ssid is not None:
|
|
|
|
if dev.actual_ssid != dev.configured_ssid:
|
|
|
|
r.append(Text("Associated to '%s', will associate to '%s'"%(dev.actual_ssid, dev.configured_ssid)))
|
|
|
|
else:
|
|
|
|
r.append(Text("Associated to '" + dev.actual_ssid + "'"))
|
|
|
|
else:
|
|
|
|
r.append(Text("No access point configured, but associated to '%s'"%(dev.actual_ssid,)))
|
|
|
|
else:
|
|
|
|
if dev.configured_ssid is not None:
|
|
|
|
r.append(Text("Will associate to '" + dev.configured_ssid + "'"))
|
|
|
|
else:
|
|
|
|
r.append(Text("No access point configured"))
|
|
|
|
return r
|
|
|
|
|
|
|
|
def _format_address_list(label, addresses):
|
|
|
|
if len(addresses) == 0:
|
|
|
|
return []
|
|
|
|
elif len(addresses) == 1:
|
|
|
|
return [Text(label%('',)+' '+str(addresses[0]))]
|
|
|
|
else:
|
2016-11-10 22:29:57 +00:00
|
|
|
ips = []
|
2016-11-07 00:35:42 +00:00
|
|
|
for ip in addresses:
|
2016-11-10 22:29:57 +00:00
|
|
|
ips.append(str(ip))
|
|
|
|
return [Text(label%('es',) + ' ' + ', '.join(ips))]
|
2016-11-07 00:35:42 +00:00
|
|
|
|
2016-12-12 02:54:33 +00:00
|
|
|
|
|
|
|
def _build_gateway_ip_info_for_version(dev, version):
|
|
|
|
actual_ip_addresses = dev.actual_ip_addresses_for_version(version)
|
|
|
|
configured_ip_addresses = dev.configured_ip_addresses_for_version(version)
|
|
|
|
if dev.dhcp_for_version(version):
|
|
|
|
if dev.actual_ip_addresses:
|
|
|
|
return _format_address_list("Will use DHCP for IPv%s, currently has address%%s:"%(version,), actual_ip_addresses)
|
|
|
|
return [Text("Will use DHCP for IPv%s"%(version,))]
|
|
|
|
elif configured_ip_addresses:
|
|
|
|
if sorted(actual_ip_addresses) == sorted(configured_ip_addresses):
|
|
|
|
return _format_address_list("Using static address%%s for IPv%s:"%(version,), actual_ip_addresses)
|
|
|
|
p = _format_address_list("Will use static address%%s for IPv%s:"%(version,), configured_ip_addresses)
|
|
|
|
if actual_ip_addresses:
|
|
|
|
p.extend(_format_address_list("Currently has address%s:", actual_ip_addresses))
|
2016-11-07 00:35:42 +00:00
|
|
|
return p
|
2016-12-12 02:54:33 +00:00
|
|
|
elif actual_ip_addresses:
|
2017-01-11 02:42:16 +00:00
|
|
|
return _format_address_list("Has no IPv%s configuration, currently has address%%s:"%(version,), actual_ip_addresses)
|
2016-11-07 00:35:42 +00:00
|
|
|
else:
|
2016-12-12 02:54:33 +00:00
|
|
|
return [Text("IPv%s is not configured"%(version,))]
|
2016-11-07 00:35:42 +00:00
|
|
|
|
2016-08-22 04:46:56 +00:00
|
|
|
|
2016-07-26 02:21:24 +00:00
|
|
|
class NetworkView(BaseView):
|
2016-10-05 02:11:32 +00:00
|
|
|
def __init__(self, model, controller):
|
2015-06-24 19:44:31 +00:00
|
|
|
self.model = model
|
2016-10-05 02:11:32 +00:00
|
|
|
self.controller = controller
|
2015-06-24 19:44:31 +00:00
|
|
|
self.items = []
|
2016-08-16 19:25:58 +00:00
|
|
|
self.error = Text("", align='center')
|
2016-11-07 00:35:42 +00:00
|
|
|
self.model_inputs = Pile(self._build_model_inputs())
|
|
|
|
self.additional_options = Pile(self._build_additional_options())
|
2015-06-24 19:44:31 +00:00
|
|
|
self.body = [
|
2017-09-07 21:42:58 +00:00
|
|
|
Padding.center_90(self.model_inputs),
|
2016-11-07 00:35:42 +00:00
|
|
|
Padding.center_79(self.additional_options),
|
2017-09-08 00:36:44 +00:00
|
|
|
Padding.line_break(""),
|
2015-06-24 19:44:31 +00:00
|
|
|
]
|
2016-06-22 19:19:54 +00:00
|
|
|
self.lb = ListBox(self.body)
|
2017-09-07 21:42:58 +00:00
|
|
|
self.footer = Pile([
|
|
|
|
Text(""),
|
|
|
|
Padding.fixed_10(self._build_buttons()),
|
|
|
|
Text(""),
|
|
|
|
])
|
|
|
|
self.frame = Pile([
|
|
|
|
self.lb,
|
|
|
|
('pack', self.footer)])
|
|
|
|
self.frame.focus_position = 1
|
|
|
|
super().__init__(self.frame)
|
2015-06-24 19:44:31 +00:00
|
|
|
|
|
|
|
def _build_buttons(self):
|
2017-02-08 02:37:18 +00:00
|
|
|
cancel = Color.button(cancel_btn(on_press=self.cancel))
|
|
|
|
done = Color.button(done_btn(on_press=self.done))
|
2016-06-22 19:19:54 +00:00
|
|
|
self.default_focus = done
|
2015-10-08 22:10:54 +00:00
|
|
|
|
2016-06-22 19:19:54 +00:00
|
|
|
buttons = [done, cancel]
|
|
|
|
return Pile(buttons, focus_item=done)
|
2015-06-24 19:44:31 +00:00
|
|
|
|
|
|
|
def _build_model_inputs(self):
|
2016-11-07 00:35:42 +00:00
|
|
|
netdevs = self.model.get_all_netdevs()
|
2016-06-22 19:19:54 +00:00
|
|
|
ifname_width = 8 # default padding
|
2016-11-07 00:35:42 +00:00
|
|
|
if netdevs:
|
|
|
|
ifname_width += max(map(lambda dev: len(dev.name), netdevs))
|
2016-09-05 09:07:56 +00:00
|
|
|
if ifname_width > 20:
|
|
|
|
ifname_width = 20
|
2015-06-24 19:44:31 +00:00
|
|
|
|
2016-09-05 09:07:56 +00:00
|
|
|
iface_menus = []
|
2016-11-07 00:35:42 +00:00
|
|
|
|
2016-08-09 19:11:02 +00:00
|
|
|
# Display each interface -- name in first column, then configured IPs
|
|
|
|
# in the second.
|
2016-11-07 00:35:42 +00:00
|
|
|
log.debug('interfaces: {}'.format(netdevs))
|
|
|
|
for dev in netdevs:
|
2016-09-05 09:07:56 +00:00
|
|
|
col_1 = []
|
|
|
|
col_2 = []
|
|
|
|
|
2015-06-29 20:01:16 +00:00
|
|
|
col_1.append(
|
2017-02-08 02:37:18 +00:00
|
|
|
Color.menu_button(
|
|
|
|
menu_btn(label=dev.name, on_press=self.on_net_dev_press)))
|
2015-06-29 20:01:16 +00:00
|
|
|
|
2016-11-07 00:35:42 +00:00
|
|
|
if dev.type == 'wlan':
|
|
|
|
col_2.extend(_build_wifi_info(dev))
|
|
|
|
if len(dev.actual_ip_addresses) == 0 and dev.type == 'eth' and not dev.is_connected:
|
|
|
|
col_2.append(Color.info_primary(Text("Not connected")))
|
2016-12-12 02:54:33 +00:00
|
|
|
col_2.extend(_build_gateway_ip_info_for_version(dev, 4))
|
|
|
|
col_2.extend(_build_gateway_ip_info_for_version(dev, 6))
|
2016-09-05 00:03:06 +00:00
|
|
|
|
2016-08-09 19:11:02 +00:00
|
|
|
# Other device info (MAC, vendor/model, speed)
|
2015-09-03 16:48:40 +00:00
|
|
|
template = ''
|
2016-11-07 00:35:42 +00:00
|
|
|
if dev.hwaddr:
|
|
|
|
template += '{} '.format(dev.hwaddr)
|
|
|
|
if dev.is_bond_slave:
|
2015-09-03 16:48:40 +00:00
|
|
|
template += '(Bonded) '
|
2016-11-07 00:35:42 +00:00
|
|
|
if not dev.vendor.lower().startswith('unknown'):
|
|
|
|
vendor = textwrap.wrap(dev.vendor, 15)[0]
|
2015-11-06 15:19:44 +00:00
|
|
|
template += '{} '.format(vendor)
|
2016-11-07 00:35:42 +00:00
|
|
|
if not dev.model.lower().startswith('unknown'):
|
|
|
|
model = textwrap.wrap(dev.model, 20)[0]
|
2015-11-06 15:19:44 +00:00
|
|
|
template += '{} '.format(model)
|
2016-11-07 00:35:42 +00:00
|
|
|
if dev.speed:
|
|
|
|
template += '({})'.format(dev.speed)
|
2016-08-09 19:11:02 +00:00
|
|
|
|
2016-06-30 21:24:31 +00:00
|
|
|
col_2.append(Color.info_minor(Text(template)))
|
2016-09-05 09:07:56 +00:00
|
|
|
iface_menus.append(Columns([(ifname_width, Pile(col_1)), Pile(col_2)], 2))
|
2015-11-06 16:36:58 +00:00
|
|
|
|
2016-11-07 00:35:42 +00:00
|
|
|
return iface_menus
|
2015-06-24 19:44:31 +00:00
|
|
|
|
2016-11-07 02:03:27 +00:00
|
|
|
def refresh_model_inputs(self):
|
|
|
|
self.model_inputs.contents = [ (obj, ('pack', None)) for obj in self._build_model_inputs() ]
|
|
|
|
self.additional_options.contents = [ (obj, ('pack', None)) for obj in self._build_additional_options() ]
|
|
|
|
|
2015-06-24 19:44:31 +00:00
|
|
|
def _build_additional_options(self):
|
2016-08-16 19:01:51 +00:00
|
|
|
labels = []
|
2016-11-07 00:35:42 +00:00
|
|
|
netdevs = self.model.get_all_netdevs()
|
2015-11-06 19:38:21 +00:00
|
|
|
|
|
|
|
# Display default route status
|
2016-08-16 16:59:19 +00:00
|
|
|
if self.model.default_v4_gateway is not None:
|
|
|
|
v4_route_source = "via " + self.model.default_v4_gateway
|
|
|
|
|
|
|
|
default_v4_route_w = Color.info_minor(
|
|
|
|
Text(" IPv4 default route " + v4_route_source + "."))
|
2016-08-16 19:01:51 +00:00
|
|
|
labels.append(default_v4_route_w)
|
2016-11-07 00:35:42 +00:00
|
|
|
|
2016-08-16 16:59:19 +00:00
|
|
|
if self.model.default_v6_gateway is not None:
|
|
|
|
v6_route_source = "via " + self.model.default_v6_gateway
|
|
|
|
|
|
|
|
default_v6_route_w = Color.info_minor(
|
|
|
|
Text(" IPv6 default route " + v6_route_source + "."))
|
2016-08-16 19:01:51 +00:00
|
|
|
labels.append(default_v6_route_w)
|
2015-11-06 19:38:21 +00:00
|
|
|
|
2016-08-16 19:01:51 +00:00
|
|
|
max_btn_len = 0
|
|
|
|
buttons = []
|
2016-09-27 03:26:04 +00:00
|
|
|
for opt, sig in self.model.get_menu():
|
2015-11-06 19:38:21 +00:00
|
|
|
if ':set-default-route' in sig:
|
2016-11-07 00:35:42 +00:00
|
|
|
if len(netdevs) < 2:
|
2015-11-06 19:38:21 +00:00
|
|
|
log.debug('Skipping default route menu option'
|
|
|
|
' (only one nic)')
|
|
|
|
continue
|
2015-11-09 20:35:54 +00:00
|
|
|
if ':bond-interfaces' in sig:
|
2016-11-07 00:35:42 +00:00
|
|
|
not_bonded = [dev for dev in netdevs if not dev.is_bonded]
|
2015-11-09 20:35:54 +00:00
|
|
|
if len(not_bonded) < 2:
|
|
|
|
log.debug('Skipping bonding menu option'
|
|
|
|
' (not enough available nics)')
|
|
|
|
continue
|
2016-08-16 19:01:51 +00:00
|
|
|
|
|
|
|
if len(opt) > max_btn_len:
|
|
|
|
max_btn_len = len(opt)
|
|
|
|
|
|
|
|
buttons.append(
|
2015-08-24 15:51:10 +00:00
|
|
|
Color.menu_button(
|
2015-08-28 14:33:25 +00:00
|
|
|
menu_btn(label=opt,
|
2016-09-27 03:26:04 +00:00
|
|
|
on_press=self.additional_menu_select,
|
2017-02-08 02:37:18 +00:00
|
|
|
user_data=sig)))
|
2016-08-16 19:01:51 +00:00
|
|
|
|
2016-11-07 00:35:42 +00:00
|
|
|
from urwid import Padding
|
|
|
|
buttons = [ Padding(button, align='left', width=max_btn_len + 6) for button in buttons ]
|
2017-09-08 00:36:44 +00:00
|
|
|
r = labels + buttons
|
|
|
|
if len(r) > 0:
|
|
|
|
r[0:0] = [Text("")]
|
|
|
|
return r
|
2015-06-24 19:44:31 +00:00
|
|
|
|
2016-09-27 03:26:04 +00:00
|
|
|
def additional_menu_select(self, result, sig):
|
2016-10-05 02:11:32 +00:00
|
|
|
self.controller.signal.emit_signal(sig)
|
2015-06-24 19:44:31 +00:00
|
|
|
|
2015-07-21 21:04:28 +00:00
|
|
|
def on_net_dev_press(self, result):
|
|
|
|
log.debug("Selected network dev: {}".format(result.label))
|
2016-10-05 02:11:32 +00:00
|
|
|
self.controller.network_configure_interface(result.label)
|
2015-10-08 22:10:54 +00:00
|
|
|
|
2016-11-07 21:39:54 +00:00
|
|
|
def show_network_error(self, action, info=None):
|
2017-09-07 21:42:58 +00:00
|
|
|
self.footer.contents[0:0] = [
|
|
|
|
(Text(""), self.footer.options()),
|
|
|
|
(Color.info_error(self.error), self.footer.options()),
|
|
|
|
]
|
2016-08-18 20:11:10 +00:00
|
|
|
if action == 'generate':
|
2016-11-07 21:39:54 +00:00
|
|
|
self.error.set_text("Network configuration failed: %r" % (info,))
|
2016-08-18 20:11:10 +00:00
|
|
|
elif action == 'apply':
|
|
|
|
self.error.set_text("Network configuration could not be applied; " + \
|
|
|
|
"please verify your settings.")
|
|
|
|
elif action == 'timeout':
|
|
|
|
self.error.set_text("Network configuration timed out; " + \
|
|
|
|
"please verify your settings.")
|
2016-08-22 04:46:56 +00:00
|
|
|
elif action == 'canceled':
|
|
|
|
self.error.set_text("Network configuration canceled.")
|
2016-08-18 20:11:10 +00:00
|
|
|
else:
|
|
|
|
self.error.set_text("An unexpected error has occurred; " + \
|
|
|
|
"please verify your settings.")
|
2016-08-16 19:25:58 +00:00
|
|
|
|
2015-10-08 22:10:54 +00:00
|
|
|
def done(self, result):
|
2016-10-05 02:11:32 +00:00
|
|
|
self.controller.network_finish(self.model.render())
|
2015-07-21 21:04:28 +00:00
|
|
|
|
2017-04-04 04:20:56 +00:00
|
|
|
def cancel(self, button=None):
|
2016-10-10 23:48:28 +00:00
|
|
|
self.controller.cancel()
|