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
|
2018-06-27 01:31:11 +00:00
|
|
|
from socket import AF_INET, AF_INET6
|
2016-08-22 04:46:56 +00:00
|
|
|
|
2017-02-03 01:20:05 +00:00
|
|
|
from urwid import (
|
2018-06-26 03:11:42 +00:00
|
|
|
connect_signal,
|
2017-02-03 01:20:05 +00:00
|
|
|
LineBox,
|
|
|
|
ProgressBar,
|
|
|
|
Text,
|
|
|
|
)
|
2016-08-22 04:46:56 +00:00
|
|
|
|
2018-06-26 03:11:42 +00:00
|
|
|
from subiquitycore.ui.actionmenu import ActionMenu
|
2018-06-27 00:06:28 +00:00
|
|
|
from subiquitycore.ui.buttons import back_btn, cancel_btn, done_btn
|
2018-06-21 21:38:18 +00:00
|
|
|
from subiquitycore.ui.container import (
|
|
|
|
ListBox,
|
|
|
|
Pile,
|
|
|
|
WidgetWrap,
|
|
|
|
)
|
2018-06-26 03:11:42 +00:00
|
|
|
from subiquitycore.ui.table import ColSpec, TablePile, TableRow
|
|
|
|
from subiquitycore.ui.utils import button_pile, Color, make_action_menu_row, Padding
|
2018-06-27 00:06:28 +00:00
|
|
|
from .network_configure_manual_interface import EditNetworkStretchy
|
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
|
2017-11-22 00:26:55 +00:00
|
|
|
button = cancel_btn(_("Cancel"), on_press=self.do_cancel)
|
2016-08-22 04:46:56 +00:00
|
|
|
self.bar = ProgressBar(normal='progress_incomplete',
|
2018-05-21 21:44:02 +00:00
|
|
|
complete='progress_complete',
|
|
|
|
current=0, done=step_count)
|
2016-08-22 04:46:56 +00:00
|
|
|
box = LineBox(Pile([self.bar,
|
2017-09-20 13:01:42 +00:00
|
|
|
button_pile([button])]),
|
2017-09-15 19:00:29 +00:00
|
|
|
title=_("Applying network config"))
|
2016-08-22 04:46:56 +00:00
|
|
|
super().__init__(box)
|
|
|
|
|
|
|
|
def advance(self):
|
|
|
|
self.bar.current += 1
|
|
|
|
|
|
|
|
def do_cancel(self, sender):
|
|
|
|
self.cancel_func()
|
|
|
|
|
2018-05-21 21:44:02 +00:00
|
|
|
|
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:
|
2018-05-21 21:44:02 +00:00
|
|
|
r.append(
|
|
|
|
Text(_("Associated to '%s', will "
|
|
|
|
"associate to '%s'" % (dev.actual_ssid,
|
|
|
|
dev.configured_ssid))))
|
2016-11-07 00:35:42 +00:00
|
|
|
else:
|
2017-09-15 19:00:29 +00:00
|
|
|
r.append(Text(_("Associated to '%s'" % dev.actual_ssid)))
|
2016-11-07 00:35:42 +00:00
|
|
|
else:
|
2018-05-21 21:44:02 +00:00
|
|
|
r.append(Text(_("No access point configured, but associated "
|
|
|
|
"to '%s'" % dev.actual_ssid)))
|
2016-11-07 00:35:42 +00:00
|
|
|
else:
|
|
|
|
if dev.configured_ssid is not None:
|
2017-09-15 19:00:29 +00:00
|
|
|
r.append(Text(_("Will associate to '%s'" % dev.configured_ssid)))
|
2016-11-07 00:35:42 +00:00
|
|
|
else:
|
2017-09-15 19:00:29 +00:00
|
|
|
r.append(Text(_("No access point configured")))
|
2016-11-07 00:35:42 +00:00
|
|
|
return r
|
|
|
|
|
2018-05-21 21:44:02 +00:00
|
|
|
|
2016-11-07 00:35:42 +00:00
|
|
|
def _format_address_list(label, addresses):
|
|
|
|
if len(addresses) == 0:
|
|
|
|
return []
|
|
|
|
elif len(addresses) == 1:
|
2018-05-21 21:44:02 +00:00
|
|
|
return [Text(label % ('',) + ' ' + str(addresses[0]))]
|
2016-11-07 00:35:42 +00:00
|
|
|
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))
|
2018-05-21 21:44:02 +00:00
|
|
|
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):
|
2018-04-27 02:56:00 +00:00
|
|
|
if dev.actual_ip_addresses_for_version(version):
|
2018-05-21 21:44:02 +00:00
|
|
|
return _format_address_list(_("Will use DHCP for IPv%s, currently "
|
|
|
|
"has address%%s:" % version),
|
|
|
|
actual_ip_addresses)
|
2017-09-15 19:00:29 +00:00
|
|
|
return [Text(_("Will use DHCP for IPv%s" % version))]
|
2016-12-12 02:54:33 +00:00
|
|
|
elif configured_ip_addresses:
|
|
|
|
if sorted(actual_ip_addresses) == sorted(configured_ip_addresses):
|
2018-05-21 21:44:02 +00:00
|
|
|
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)
|
2016-12-12 02:54:33 +00:00
|
|
|
if actual_ip_addresses:
|
2018-05-21 21:44:02 +00:00
|
|
|
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:
|
2018-05-21 21:44:02 +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:
|
2017-09-15 19:00:29 +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):
|
2018-05-18 03:55:43 +00:00
|
|
|
title = _("Network connections")
|
2018-05-24 16:00:49 +00:00
|
|
|
excerpt = _("Configure at least one interface this server can use to talk "
|
|
|
|
"to other machines, and which preferably provides sufficient "
|
|
|
|
"access for updates.")
|
|
|
|
footer = _("Select an interface to configure it or select Done to "
|
|
|
|
"continue")
|
2018-05-18 03:55:43 +00:00
|
|
|
|
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')
|
2018-06-26 03:11:42 +00:00
|
|
|
self.device_table = TablePile(self._build_model_inputs(), spacing=2, colspecs={3:ColSpec(can_shrink=True)})
|
|
|
|
self.listbox = ListBox([self.device_table] + [
|
2017-09-08 00:36:44 +00:00
|
|
|
Padding.line_break(""),
|
2018-03-16 02:30:14 +00:00
|
|
|
])
|
2018-05-18 03:55:43 +00:00
|
|
|
self.bottom = Pile([
|
2017-09-07 21:42:58 +00:00
|
|
|
Text(""),
|
2017-09-20 02:55:02 +00:00
|
|
|
self._build_buttons(),
|
2017-09-07 21:42:58 +00:00
|
|
|
Text(""),
|
|
|
|
])
|
2017-09-28 20:00:35 +00:00
|
|
|
self.error_showing = False
|
2017-09-07 21:42:58 +00:00
|
|
|
self.frame = Pile([
|
2018-05-18 03:55:43 +00:00
|
|
|
('pack', Text("")),
|
|
|
|
('pack', Padding.center_79(Text(_(self.excerpt)))),
|
2017-10-20 01:55:01 +00:00
|
|
|
('pack', Text("")),
|
2018-03-16 02:30:14 +00:00
|
|
|
Padding.center_90(self.listbox),
|
2018-05-18 03:55:43 +00:00
|
|
|
('pack', self.bottom)])
|
|
|
|
self.frame.focus_position = 4
|
2017-09-07 21:42:58 +00:00
|
|
|
super().__init__(self.frame)
|
2015-06-24 19:44:31 +00:00
|
|
|
|
|
|
|
def _build_buttons(self):
|
2017-11-22 00:26:55 +00:00
|
|
|
back = back_btn(_("Back"), on_press=self.cancel)
|
|
|
|
done = done_btn(_("Done"), on_press=self.done)
|
2018-04-09 02:10:36 +00:00
|
|
|
return button_pile([done, back])
|
2015-06-24 19:44:31 +00:00
|
|
|
|
2018-06-26 03:11:42 +00:00
|
|
|
def _action_info(self, device):
|
|
|
|
pass
|
|
|
|
|
2018-06-26 03:22:23 +00:00
|
|
|
def _action_edit_ipv4(self, device):
|
|
|
|
self.show_stretchy_overlay(EditNetworkStretchy(self, device, 4))
|
|
|
|
|
2018-06-26 04:29:55 +00:00
|
|
|
def _action_edit_ipv6(self, device):
|
|
|
|
self.show_stretchy_overlay(EditNetworkStretchy(self, device, 6))
|
|
|
|
|
2018-06-26 03:11:42 +00:00
|
|
|
def _action(self, sender, action, device):
|
|
|
|
m = getattr(self, '_action_{}'.format(action))
|
|
|
|
m(device)
|
|
|
|
|
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()
|
2018-06-26 03:11:42 +00:00
|
|
|
rows = []
|
|
|
|
rows.append(TableRow([Color.info_minor(header) for header in [Text(" NAME"), Text("TYPE"), Text("DHCP"), Text("ADDRESSES")]]))
|
|
|
|
for dev in netdevs:
|
|
|
|
dhcp = []
|
|
|
|
if dev.dhcp4:
|
|
|
|
dhcp.append('v4')
|
|
|
|
if dev.dhcp6:
|
|
|
|
dhcp.append('v6')
|
|
|
|
if dhcp:
|
|
|
|
dhcp = ",".join(dhcp)
|
|
|
|
else:
|
|
|
|
dhcp = '-'
|
2018-06-27 01:31:11 +00:00
|
|
|
addresses = []
|
|
|
|
for v in 4, 6:
|
|
|
|
if dev.configured_ip_addresses_for_version(v):
|
|
|
|
addresses.extend(["{} (static)".format(a) for a in dev.configured_ip_addresses_for_version(v)])
|
|
|
|
elif dev.dhcp_for_version(v):
|
|
|
|
if v == 4:
|
|
|
|
fam = AF_INET
|
|
|
|
elif v == 6:
|
|
|
|
fam = AF_INET6
|
|
|
|
for a in dev._net_info.addresses.values():
|
|
|
|
log.debug("a %s", a.serialize())
|
|
|
|
if a.family == fam and a.source == 'dhcp':
|
|
|
|
addresses.append("{} (from dhcp)".format(a.address))
|
|
|
|
if addresses:
|
|
|
|
addresses = ", ".join(addresses)
|
2018-06-26 03:11:42 +00:00
|
|
|
else:
|
|
|
|
addresses = '-'
|
|
|
|
actions = [
|
2018-06-26 03:22:23 +00:00
|
|
|
("Info", True, 'info', True),
|
|
|
|
("Edit IPv4", True, 'edit_ipv4', True),
|
2018-06-26 04:29:55 +00:00
|
|
|
("Edit IPv6", True, 'edit_ipv6', True),
|
2018-06-26 03:11:42 +00:00
|
|
|
]
|
|
|
|
menu = ActionMenu(actions)
|
|
|
|
connect_signal(menu, 'action', self._action, dev)
|
|
|
|
rows.append(make_action_menu_row(
|
|
|
|
[
|
|
|
|
Text(dev.name),
|
|
|
|
Text(dev.type),
|
|
|
|
Text(dhcp),
|
|
|
|
Text(addresses, wrap='clip'),
|
|
|
|
],
|
|
|
|
menu,
|
|
|
|
))
|
2018-06-27 01:31:11 +00:00
|
|
|
info = " / ".join([dev.hwaddr, dev.vendor, dev.model])
|
|
|
|
rows.append(Color.info_minor(TableRow([(4, Text(" " + info))])))
|
2018-06-26 03:11:42 +00:00
|
|
|
rows.append(Color.info_minor(TableRow([(4, Text(""))])))
|
|
|
|
return rows
|
2015-06-24 19:44:31 +00:00
|
|
|
|
2016-11-07 02:03:27 +00:00
|
|
|
def refresh_model_inputs(self):
|
2018-06-26 03:11:42 +00:00
|
|
|
self.device_table.set_contents(self._build_model_inputs())
|
2016-11-07 02:03:27 +00:00
|
|
|
|
2016-11-07 21:39:54 +00:00
|
|
|
def show_network_error(self, action, info=None):
|
2017-09-28 20:00:35 +00:00
|
|
|
self.error_showing = True
|
2018-05-18 03:55:43 +00:00
|
|
|
self.bottom.contents[0:0] = [
|
|
|
|
(Text(""), self.bottom.options()),
|
|
|
|
(Color.info_error(self.error), self.bottom.options()),
|
2017-09-07 21:42:58 +00:00
|
|
|
]
|
2017-09-28 15:51:32 +00:00
|
|
|
if action == 'stop-networkd':
|
2018-05-20 23:52:06 +00:00
|
|
|
exc = info[0]
|
2018-05-21 21:44:02 +00:00
|
|
|
self.error.set_text(
|
|
|
|
"Stopping systemd-networkd-failed: %r" % (exc.stderr,))
|
2016-08-18 20:11:10 +00:00
|
|
|
elif action == 'apply':
|
2018-05-21 21:44:02 +00:00
|
|
|
self.error.set_text("Network configuration could not be applied; "
|
2016-08-18 20:11:10 +00:00
|
|
|
"please verify your settings.")
|
|
|
|
elif action == 'timeout':
|
2018-05-21 21:44:02 +00:00
|
|
|
self.error.set_text("Network configuration timed out; "
|
2016-08-18 20:11:10 +00:00
|
|
|
"please verify your settings.")
|
2017-09-28 15:51:32 +00:00
|
|
|
elif action == 'down':
|
|
|
|
self.error.set_text("Downing network interfaces failed.")
|
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:
|
2018-05-21 21:44:02 +00:00
|
|
|
self.error.set_text("An unexpected error has occurred; "
|
2016-08-18 20:11:10 +00:00
|
|
|
"please verify your settings.")
|
2016-08-16 19:25:58 +00:00
|
|
|
|
2015-10-08 22:10:54 +00:00
|
|
|
def done(self, result):
|
2017-09-28 20:00:35 +00:00
|
|
|
if self.error_showing:
|
2018-05-18 03:55:43 +00:00
|
|
|
self.bottom.contents[0:2] = []
|
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()
|