Exemplo n.º 1
0
class NetworkView(BaseView):
    title = _("Network connections")
    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")

    def __init__(self, model, controller):
        self.model = model
        self.controller = controller
        self.dev_to_row = {}
        self.cur_netdevs = []
        self.error = Text("", align='center')
        self.device_table = TablePile(self._build_model_inputs(),
                                      spacing=2,
                                      colspecs={
                                          0: ColSpec(rpad=1),
                                          4: ColSpec(can_shrink=True, rpad=1),
                                      })

        self._create_bond_btn = menu_btn(_("Create bond"),
                                         on_press=self._create_bond)
        bp = button_pile([self._create_bond_btn])
        bp.align = 'left'

        self.listbox = ListBox([self.device_table] + [
            bp,
        ])
        self.bottom = Pile([
            Text(""),
            self._build_buttons(),
            Text(""),
        ])
        self.error_showing = False

        self.frame = Pile([('pack', Text("")),
                           ('pack', Padding.center_79(Text(_(self.excerpt)))),
                           ('pack', Text("")),
                           Padding.center_90(self.listbox),
                           ('pack', self.bottom)])
        self.frame.set_focus(self.bottom)
        super().__init__(self.frame)

    def _build_buttons(self):
        back = back_btn(_("Back"), on_press=self.cancel)
        done = done_btn(_("Done"), on_press=self.done)
        return button_pile([done, back])

    _action_INFO = _stretchy_shower(ViewInterfaceInfo)
    _action_EDIT_WLAN = _stretchy_shower(NetworkConfigureWLANStretchy)
    _action_EDIT_IPV4 = _stretchy_shower(EditNetworkStretchy, 4)
    _action_EDIT_IPV6 = _stretchy_shower(EditNetworkStretchy, 6)
    _action_EDIT_BOND = _stretchy_shower(BondStretchy)
    _action_ADD_VLAN = _stretchy_shower(AddVlanStretchy)

    def _action_DELETE(self, device):
        self.controller.rm_virtual_interface(device)

    def _action(self, sender, action, device):
        action, meth = action
        log.debug("_action %s %s", action.name, device.name)
        meth(device)

    def _cells_for_device(self, dev):
        notes = []
        if dev.is_bond_slave:
            notes.append(
                _("enslaved to {}").format(dev._net_info.bond['master']))
        for v in 4, 6:
            if dev.configured_ip_addresses_for_version(v):
                notes.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
                fam_addresses = []
                for a in dev._net_info.addresses.values():
                    log.debug("a %s", a.serialize())
                    if a.family == fam and a.source == 'dhcp':
                        fam_addresses.append("{} (from dhcp)".format(
                            a.address))
                if fam_addresses:
                    notes.extend(fam_addresses)
                else:
                    notes.append(
                        _("DHCPv{v} has supplied no addresses").format(v=v))
        if notes:
            notes = ", ".join(notes)
        else:
            notes = '-'
        return (dev.name, dev.type, notes)

    def new_link(self, new_dev):
        for i, cur_dev in enumerate(self.cur_netdevs):
            if cur_dev.name > new_dev.name:
                netdev_i = i
                break
        else:
            netdev_i = len(self.cur_netdevs)
        new_rows = self._rows_for_device(new_dev, netdev_i)
        self.device_table.insert_rows(3 * netdev_i + 1, new_rows)

    def update_link(self, dev):
        row = self.dev_to_row[dev]
        for i, text in enumerate(self._cells_for_device(dev)):
            row.columns[2 * (i + 1)].set_text(text)

    def del_link(self, dev):
        log.debug("del_link %s", (dev in self.cur_netdevs))
        if dev in self.cur_netdevs:
            netdev_i = self.cur_netdevs.index(dev)
            self.device_table.remove_rows(3 * netdev_i, 3 * (netdev_i + 1))
            del self.cur_netdevs[netdev_i]
        if isinstance(self._w, StretchyOverlay):
            stretchy = self._w.stretchy
            if getattr(stretchy, 'device', None) is dev:
                self.remove_overlay()

    def _rows_for_device(self, dev, netdev_i=None):
        if netdev_i is None:
            netdev_i = len(self.cur_netdevs)
        rows = []
        name, typ, addresses = self._cells_for_device(dev)
        actions = []
        for action in NetDevAction:
            meth = getattr(self, '_action_' + action.name)
            opens_dialog = getattr(meth, 'opens_dialog', False)
            if dev.supports_action(action):
                actions.append(
                    (_(action.value), True, (action, meth), opens_dialog))
        menu = ActionMenu(actions)
        connect_signal(menu, 'action', self._action, dev)
        row = make_action_menu_row([
            Text("["),
            Text(name),
            Text(typ),
            Text(addresses, wrap='clip'),
            menu,
            Text("]"),
        ], menu)
        self.dev_to_row[dev] = row.base_widget
        self.cur_netdevs[netdev_i:netdev_i] = [dev]
        rows.append(row)
        if dev.type == "vlan":
            info = _("VLAN {id} on interface {link}").format(
                **dev._configuration)
        elif dev.type == "bond":
            info = _("bond master for {}").format(', '.join(
                dev._net_info.bond['slaves']))
        else:
            info = " / ".join([dev.hwaddr, dev.vendor, dev.model])
        rows.append(
            Color.info_minor(TableRow([Text(""), (4, Text(info)),
                                       Text("")])))
        rows.append(Color.info_minor(TableRow([(4, Text(""))])))
        return rows

    def _build_model_inputs(self):
        netdevs = self.model.get_all_netdevs()
        masters = []
        for master in netdevs:
            if not master._net_info.bond['is_master']:
                continue
            masters.append((_("Set master to %s") % master.name, True, {
                'action': 'add_master',
                'master': master
            }, False))
        rows = []
        rows.append(
            TableRow([
                Color.info_minor(Text(header))
                for header in ["", "NAME", "TYPE", "NOTES / ADDRESSES", ""]
            ]))
        for dev in netdevs:
            rows.extend(self._rows_for_device(dev))
        return rows

    def _create_bond(self, sender):
        self.show_stretchy_overlay(BondStretchy(self))

    def show_network_error(self, action, info=None):
        self.error_showing = True
        self.bottom.contents[0:0] = [
            (Text(""), self.bottom.options()),
            (Color.info_error(self.error), self.bottom.options()),
        ]
        if action == 'stop-networkd':
            exc = info[0]
            self.error.set_text("Stopping systemd-networkd-failed: %r" %
                                (exc.stderr, ))
        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.")
        elif action == 'down':
            self.error.set_text("Downing network interfaces failed.")
        elif action == 'canceled':
            self.error.set_text("Network configuration canceled.")
        elif action == 'add-vlan':
            self.error.set_text("Failed to add a VLAN tag.")
        elif action == 'rm-dev':
            self.error.set_text("Failed to delete a virtual interface.")
        else:
            self.error.set_text("An unexpected error has occurred; "
                                "please verify your settings.")

    def done(self, result):
        if self.error_showing:
            self.bottom.contents[0:2] = []
        self.controller.network_finish(self.model.render())

    def cancel(self, button=None):
        self.controller.cancel()
Exemplo n.º 2
0
class NetworkDeviceTable(WidgetWrap):
    def __init__(self, parent, dev_info):
        self.parent = parent
        self.dev_info = dev_info
        super().__init__(self._create())

    def _create(self):
        # Create the widget for a nic. This consists of a Pile containing a
        # table, an info line and a blank line. The first row of the table is
        # the one that can be focused and has a menu for manipulating the nic,
        # the other rows summarize its address config.
        #   [ name type notes   ▸ ]   \
        #     address info            | <- table
        #     more address info       /
        #   mac / vendor info / model info
        #   <blank line>

        actions = []
        for action in NetDevAction:
            meth = getattr(self.parent, '_action_' + action.name)
            opens_dialog = getattr(meth, 'opens_dialog', False)
            if action in self.dev_info.enabled_actions:
                actions.append(
                    (action.str(), True, (action, meth), opens_dialog))

        menu = ActionMenu(actions)
        connect_signal(menu, 'action', self.parent._action, self)

        trows = [
            make_action_menu_row([
                Text("["),
                Text(self.dev_info.name),
                Text(self.dev_info.type),
                Text(self._notes(), wrap='clip'),
                menu,
                Text("]"),
            ], menu)
        ] + self._address_rows()

        self.table = TablePile(trows,
                               colspecs=self.parent.device_colspecs,
                               spacing=2)
        self.table.bind(self.parent.heading_table)

        if self.dev_info.type == "vlan":
            info = _("VLAN {id} on interface {link}").format(
                id=self.dev_info.vlan.id, link=self.dev_info.vlan.link)
        elif self.dev_info.type == "bond":
            info = _("bond master for {interfaces}").format(
                interfaces=', '.join(self.dev_info.bond.interfaces))
        else:
            info = " / ".join([
                self.dev_info.hwaddr,
                self.dev_info.vendor,
                self.dev_info.model,
            ])

        return Pile([
            ('pack', self.table),
            ('pack', Color.info_minor(Text("  " + info))),
            ('pack', Text("")),
        ])

    def _notes(self):
        notes = []
        if self.dev_info.type == "wlan":
            config = self.dev_info.wlan.config
            if config.ssid is not None:
                notes.append(_("ssid: {ssid}".format(ssid=config.ssid)))
            else:
                notes.append(_("not connected"))
        if not self.dev_info.is_connected:
            notes.append(_("not connected"))
        if self.dev_info.bond_master:
            notes.append(
                _("enslaved to {device}").format(
                    device=self.dev_info.bond_master))
        if notes:
            notes = ", ".join(notes)
        else:
            notes = '-'
        return notes

    def _address_rows(self):
        address_info = []
        for v, dhcp_status, static_config in (
            (4, self.dev_info.dhcp4, self.dev_info.static4),
            (6, self.dev_info.dhcp6, self.dev_info.static6),
        ):
            if dhcp_status.enabled:
                label = Text("DHCPv{v}".format(v=v))
                addrs = dhcp_status.addresses
                if addrs:
                    address_info.extend([(label, Text(addr))
                                         for addr in addrs])
                elif dhcp_status.state == DHCPState.PENDING:
                    s = Spinner(self.parent.controller.app.aio_loop,
                                align='left')
                    s.rate = 0.3
                    s.start()
                    address_info.append((label, s))
                elif dhcp_status.state == DHCPState.TIMED_OUT:
                    address_info.append((label, Text(_("timed out"))))
                elif dhcp_status.state == DHCPState.RECONFIGURE:
                    address_info.append((label, Text("-")))
            elif static_config.addresses:
                address_info.append((
                    Text(_('static')),
                    Text(', '.join(static_config.addresses)),
                ))
        if len(address_info) == 0 and not self.dev_info.is_used:
            reason = self.dev_info.disabled_reason
            if reason is None:
                reason = ""
            address_info.append((Text(_("disabled")), Text(reason)))
        rows = []
        for label, value in address_info:
            rows.append(TableRow([Text(""), label, (2, value)]))
        return rows

    def update(self, dev_info):
        # Update the display of dev to represent the current state.
        #
        # The easiest way of doing this would be to just create a new table
        # widget for the device and replace the current one with it. But that
        # is jarring if the menu for the current device is open, so instead we
        # overwrite the contents of the first (menu) row of the table, and
        # replace all other rows of the with new content (which is OK as they
        # cannot be focused).
        self.dev_info = dev_info
        first_row = self.table.table_rows[0].base_widget
        first_row.cells[1][1].set_text(dev_info.name)
        first_row.cells[2][1].set_text(dev_info.type)
        first_row.cells[3][1].set_text(self._notes())
        self.table.remove_rows(1, len(self.table.table_rows))
        self.table.insert_rows(1, self._address_rows())