summary refs log tree commit diff
path: root/src/nmtui
diff options
context:
space:
mode:
Diffstat (limited to 'src/nmtui')
-rw-r--r--src/nmtui/meson.build68
-rw-r--r--src/nmtui/nm-editor-bindings.c1139
-rw-r--r--src/nmtui/nm-editor-bindings.h56
-rw-r--r--src/nmtui/nm-editor-utils.c467
-rw-r--r--src/nmtui/nm-editor-utils.h22
-rw-r--r--src/nmtui/nmt-address-list.c266
-rw-r--r--src/nmtui/nmt-address-list.h43
-rw-r--r--src/nmtui/nmt-connect-connection-list.c692
-rw-r--r--src/nmtui/nmt-connect-connection-list.h53
-rw-r--r--src/nmtui/nmt-device-entry.c555
-rw-r--r--src/nmtui/nmt-device-entry.h42
-rw-r--r--src/nmtui/nmt-edit-connection-list.c584
-rw-r--r--src/nmtui/nmt-edit-connection-list.h45
-rw-r--r--src/nmtui/nmt-editor-grid.c441
-rw-r--r--src/nmtui/nmt-editor-grid.h48
-rw-r--r--src/nmtui/nmt-editor-page-device.c119
-rw-r--r--src/nmtui/nmt-editor-page-device.h38
-rw-r--r--src/nmtui/nmt-editor-page.c175
-rw-r--r--src/nmtui/nmt-editor-page.h44
-rw-r--r--src/nmtui/nmt-editor-section.c266
-rw-r--r--src/nmtui/nmt-editor-section.h42
-rw-r--r--src/nmtui/nmt-editor.c534
-rw-r--r--src/nmtui/nmt-editor.h33
-rw-r--r--src/nmtui/nmt-ip-entry.c221
-rw-r--r--src/nmtui/nmt-ip-entry.h34
-rw-r--r--src/nmtui/nmt-mac-entry.c243
-rw-r--r--src/nmtui/nmt-mac-entry.h40
-rw-r--r--src/nmtui/nmt-mtu-entry.c176
-rw-r--r--src/nmtui/nmt-mtu-entry.h34
-rw-r--r--src/nmtui/nmt-page-bond.c445
-rw-r--r--src/nmtui/nmt-page-bond.h34
-rw-r--r--src/nmtui/nmt-page-bridge-port.c82
-rw-r--r--src/nmtui/nmt-page-bridge-port.h36
-rw-r--r--src/nmtui/nmt-page-bridge.c182
-rw-r--r--src/nmtui/nmt-page-bridge.h35
-rw-r--r--src/nmtui/nmt-page-dsl.c131
-rw-r--r--src/nmtui/nmt-page-dsl.h34
-rw-r--r--src/nmtui/nmt-page-ethernet.c88
-rw-r--r--src/nmtui/nmt-page-ethernet.h35
-rw-r--r--src/nmtui/nmt-page-infiniband.c96
-rw-r--r--src/nmtui/nmt-page-infiniband.h36
-rw-r--r--src/nmtui/nmt-page-ip-tunnel.c203
-rw-r--r--src/nmtui/nmt-page-ip-tunnel.h34
-rw-r--r--src/nmtui/nmt-page-ip4.c212
-rw-r--r--src/nmtui/nmt-page-ip4.h34
-rw-r--r--src/nmtui/nmt-page-ip6.c211
-rw-r--r--src/nmtui/nmt-page-ip6.h34
-rw-r--r--src/nmtui/nmt-page-ppp.c281
-rw-r--r--src/nmtui/nmt-page-ppp.h34
-rw-r--r--src/nmtui/nmt-page-team-port.c111
-rw-r--r--src/nmtui/nmt-page-team-port.h36
-rw-r--r--src/nmtui/nmt-page-team.c175
-rw-r--r--src/nmtui/nmt-page-team.h34
-rw-r--r--src/nmtui/nmt-page-vlan.c121
-rw-r--r--src/nmtui/nmt-page-vlan.h34
-rw-r--r--src/nmtui/nmt-page-wifi.c409
-rw-r--r--src/nmtui/nmt-page-wifi.h34
-rw-r--r--src/nmtui/nmt-password-dialog.c337
-rw-r--r--src/nmtui/nmt-password-dialog.h44
-rw-r--r--src/nmtui/nmt-password-fields.c307
-rw-r--r--src/nmtui/nmt-password-fields.h41
-rw-r--r--src/nmtui/nmt-route-editor.c186
-rw-r--r--src/nmtui/nmt-route-editor.h35
-rw-r--r--src/nmtui/nmt-route-entry.c286
-rw-r--r--src/nmtui/nmt-route-entry.h35
-rw-r--r--src/nmtui/nmt-route-table.c304
-rw-r--r--src/nmtui/nmt-route-table.h35
-rw-r--r--src/nmtui/nmt-slave-list.c253
-rw-r--r--src/nmtui/nmt-slave-list.h37
-rw-r--r--src/nmtui/nmt-utils.c130
-rw-r--r--src/nmtui/nmt-utils.h21
-rw-r--r--src/nmtui/nmt-widget-list.c487
-rw-r--r--src/nmtui/nmt-widget-list.h50
-rw-r--r--src/nmtui/nmtui-connect.c488
-rw-r--r--src/nmtui/nmtui-connect.h11
-rw-r--r--src/nmtui/nmtui-edit.c568
-rw-r--r--src/nmtui/nmtui-edit.h26
-rw-r--r--src/nmtui/nmtui-hostname.c106
-rw-r--r--src/nmtui/nmtui-hostname.h11
-rw-r--r--src/nmtui/nmtui.c297
-rw-r--r--src/nmtui/nmtui.h13
81 files changed, 13859 insertions, 0 deletions
diff --git a/src/nmtui/meson.build b/src/nmtui/meson.build
new file mode 100644
index 00000000..fe440175
--- /dev/null
+++ b/src/nmtui/meson.build
@@ -0,0 +1,68 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+executable(
+  'nmtui',
+  files(
+    'nm-editor-bindings.c',
+    'nm-editor-utils.c',
+    'nmt-address-list.c',
+    'nmt-connect-connection-list.c',
+    'nmt-device-entry.c',
+    'nmt-edit-connection-list.c',
+    'nmt-editor.c',
+    'nmt-editor-grid.c',
+    'nmt-editor-page.c',
+    'nmt-editor-page-device.c',
+    'nmt-editor-section.c',
+    'nmt-ip-entry.c',
+    'nmt-mac-entry.c',
+    'nmt-mtu-entry.c',
+    'nmt-page-bond.c',
+    'nmt-page-bridge.c',
+    'nmt-page-bridge-port.c',
+    'nmt-page-dsl.c',
+    'nmt-page-ethernet.c',
+    'nmt-page-infiniband.c',
+    'nmt-page-ip4.c',
+    'nmt-page-ip6.c',
+    'nmt-page-ip-tunnel.c',
+    'nmt-page-ppp.c',
+    'nmt-page-team.c',
+    'nmt-page-team-port.c',
+    'nmt-page-vlan.c',
+    'nmt-page-wifi.c',
+    'nmt-password-dialog.c',
+    'nmt-password-fields.c',
+    'nmt-route-editor.c',
+    'nmt-route-entry.c',
+    'nmt-route-table.c',
+    'nmt-slave-list.c',
+    'nmtui.c',
+    'nmtui-connect.c',
+    'nmtui-edit.c',
+    'nmtui-hostname.c',
+    'nmt-utils.c',
+    'nmt-widget-list.c',
+  ),
+  dependencies: [
+    libnm_dep,
+    newt_dep,
+    glib_dep,
+  ],
+  link_with: [
+    libnmt_newt,
+    libnmc_setting,
+    libnmc_base,
+    libnm_client_aux_extern,
+    libnm_core_aux_extern,
+    libnm_core_aux_intern,
+    libnm_base,
+    libnm_log_null,
+    libnm_glib_aux,
+    libnm_std_aux,
+    libc_siphash,
+  ],
+  link_args: ldflags_linker_script_binary,
+  link_depends: linker_script_binary,
+  install: true,
+)
diff --git a/src/nmtui/nm-editor-bindings.c b/src/nmtui/nm-editor-bindings.c
new file mode 100644
index 00000000..2b1c4748
--- /dev/null
+++ b/src/nmtui/nm-editor-bindings.c
@@ -0,0 +1,1139 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+/**
+ * SECTION:nm-editor-bindings
+ * @short_description: #GBinding-based NM connection editor helpers
+ *
+ * nm-editor-bindings contains helper functions to bind NMSettings objects
+ * to connection editing widgets. The goal is that this should eventually be
+ * shared between nmtui, nm-connection-editor, and gnome-control-center.
+ */
+
+#include "libnm-client-aux-extern/nm-default-client.h"
+
+#include "nm-editor-bindings.h"
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <stdlib.h>
+
+static void
+value_transform_string_int(const GValue *src_value, GValue *dest_value)
+{
+    long  val;
+    char *end;
+
+    val = strtol(g_value_get_string(src_value), &end, 10);
+    if (val < G_MININT || val > G_MAXINT || *end)
+        return;
+
+    g_value_set_int(dest_value, (int) val);
+}
+
+static void
+value_transform_string_uint(const GValue *src_value, GValue *dest_value)
+{
+    long  val;
+    char *end;
+
+    val = strtol(g_value_get_string(src_value), &end, 10);
+    if (val < 0 || val > G_MAXUINT || *end)
+        return;
+
+    g_value_set_uint(dest_value, (int) val);
+}
+
+void
+nm_editor_bindings_init(void)
+{
+    /* glib registers number -> string, but not string -> number */
+    g_value_register_transform_func(G_TYPE_STRING, G_TYPE_INT, value_transform_string_int);
+    g_value_register_transform_func(G_TYPE_STRING, G_TYPE_UINT, value_transform_string_uint);
+}
+
+static gboolean
+ip_addresses_with_prefix_to_strv(GBinding *    binding,
+                                 const GValue *source_value,
+                                 GValue *      target_value,
+                                 gpointer      user_data)
+{
+    GPtrArray *  addrs;
+    NMIPAddress *addr;
+    const char * addrstr;
+    guint32      prefix;
+    char **      strings;
+    int          i;
+
+    addrs   = g_value_get_boxed(source_value);
+    strings = g_new0(char *, addrs->len + 1);
+
+    for (i = 0; i < addrs->len; i++) {
+        addr    = addrs->pdata[i];
+        addrstr = nm_ip_address_get_address(addr);
+        prefix  = nm_ip_address_get_prefix(addr);
+
+        if (addrstr)
+            strings[i] = g_strdup_printf("%s/%d", addrstr, (int) prefix);
+        else
+            strings[i] = g_strdup("");
+    }
+
+    g_value_take_boxed(target_value, strings);
+    return TRUE;
+}
+
+static gboolean
+ip_addresses_with_prefix_from_strv(GBinding *    binding,
+                                   const GValue *source_value,
+                                   GValue *      target_value,
+                                   gpointer      user_data)
+{
+    int          addr_family = GPOINTER_TO_INT(user_data);
+    char **      strings;
+    GPtrArray *  addrs;
+    NMIPAddress *addr;
+    char *       addrstr;
+    int          prefix;
+    int          i;
+
+    strings = g_value_get_boxed(source_value);
+    /* Fetch the original property value, so as to preserve their extra attributes */
+    g_object_get(g_binding_get_source(binding),
+                 g_binding_get_source_property(binding),
+                 &addrs,
+                 NULL);
+
+    for (i = 0; strings[i]; i++) {
+        if (i >= addrs->len) {
+            if (addr_family == AF_INET)
+                addr = nm_ip_address_new(AF_INET, "0.0.0.0", 32, NULL);
+            else
+                addr = nm_ip_address_new(AF_INET6, "::", 128, NULL);
+            g_ptr_array_add(addrs, addr);
+        } else
+            addr = addrs->pdata[i];
+
+        if (!nm_utils_parse_inaddr_prefix(addr_family, strings[i], &addrstr, &prefix)) {
+            g_ptr_array_unref(addrs);
+            return FALSE;
+        }
+
+        if (prefix == -1) {
+            if (addr_family == AF_INET) {
+                in_addr_t v4;
+
+                inet_pton(addr_family, addrstr, &v4);
+                if (nm_utils_ip_is_site_local(AF_INET, &v4))
+                    prefix = nm_utils_ip4_get_default_prefix(v4);
+                else
+                    prefix = 32;
+            } else
+                prefix = 64;
+        }
+
+        nm_ip_address_set_address(addr, addrstr);
+        nm_ip_address_set_prefix(addr, prefix);
+        g_free(addrstr);
+    }
+
+    g_ptr_array_set_size(addrs, i);
+    g_value_take_boxed(target_value, addrs);
+    return TRUE;
+}
+
+/**
+ * nm_editor_bind_ip_addresses_with_prefix_to_strv:
+ * @addr_family: the IP address family
+ * @source: the source object (eg, an #NMSettingIP4Config)
+ * @source_property: the property on @source to bind (eg,
+ *   %NM_SETTING_IP4_CONFIG_ADDRESSES)
+ * @target: the target object (eg, an #NmtAddressList)
+ * @target_property: the property on @target to bind
+ *   (eg, "strings")
+ * @flags: %GBindingFlags
+ *
+ * Binds the #GPtrArray-of-#NMIPAddress property @source_property on @source to
+ * the %G_TYPE_STRV property @target_property on @target.
+ *
+ * Each #NMIPAddress in @source_property will be converted to a string of the
+ * form "ip.ad.dr.ess/prefix" or "ip:ad:dr:ess/prefix" in @target_property (and
+ * vice versa if %G_BINDING_BIDIRECTIONAL) is specified.
+ */
+void
+nm_editor_bind_ip_addresses_with_prefix_to_strv(int           addr_family,
+                                                gpointer      source,
+                                                const char *  source_property,
+                                                gpointer      target,
+                                                const char *  target_property,
+                                                GBindingFlags flags)
+{
+    g_object_bind_property_full(source,
+                                source_property,
+                                target,
+                                target_property,
+                                flags,
+                                ip_addresses_with_prefix_to_strv,
+                                ip_addresses_with_prefix_from_strv,
+                                GINT_TO_POINTER(addr_family),
+                                NULL);
+}
+
+static gboolean
+ip_addresses_check_and_copy(GBinding *    binding,
+                            const GValue *source_value,
+                            GValue *      target_value,
+                            gpointer      user_data)
+{
+    int    addr_family = GPOINTER_TO_INT(user_data);
+    char **strings;
+    int    i;
+
+    strings = g_value_get_boxed(source_value);
+
+    for (i = 0; strings[i]; i++) {
+        if (!nm_utils_ipaddr_is_valid(addr_family, strings[i]))
+            return FALSE;
+    }
+
+    g_value_set_boxed(target_value, strings);
+    return TRUE;
+}
+
+/**
+ * nm_editor_bind_ip_addresses_to_strv:
+ * @addr_family: the IP address family
+ * @source: the source object (eg, an #NMSettingIP4Config)
+ * @source_property: the property on @source to bind (eg,
+ *   %NM_SETTING_IP4_CONFIG_DNS)
+ * @target: the target object (eg, an #NmtAddressList)
+ * @target_property: the property on @target to bind
+ *   (eg, "strings")
+ * @flags: %GBindingFlags
+ *
+ * Binds the %G_TYPE_STRV property @source_property on @source to the
+ * %G_TYPE_STRV property @target_property on @target, verifying that
+ * each string is a valid address of type @addr_family when copying.
+ */
+void
+nm_editor_bind_ip_addresses_to_strv(int           addr_family,
+                                    gpointer      source,
+                                    const char *  source_property,
+                                    gpointer      target,
+                                    const char *  target_property,
+                                    GBindingFlags flags)
+{
+    g_object_bind_property_full(source,
+                                source_property,
+                                target,
+                                target_property,
+                                flags,
+                                ip_addresses_check_and_copy,
+                                ip_addresses_check_and_copy,
+                                GINT_TO_POINTER(addr_family),
+                                NULL);
+}
+
+static gboolean
+ip_gateway_to_string(GBinding *    binding,
+                     const GValue *source_value,
+                     GValue *      target_value,
+                     gpointer      user_data)
+{
+    g_value_set_string(target_value, g_value_get_string(source_value));
+    return TRUE;
+}
+
+static gboolean
+ip_gateway_from_string(GBinding *    binding,
+                       const GValue *source_value,
+                       GValue *      target_value,
+                       gpointer      user_data)
+{
+    int         addr_family = GPOINTER_TO_INT(user_data);
+    const char *gateway;
+
+    gateway = g_value_get_string(source_value);
+    if (gateway && !nm_utils_ipaddr_is_valid(addr_family, gateway))
+        gateway = NULL;
+
+    g_value_set_string(target_value, gateway);
+    return TRUE;
+}
+
+static gboolean
+ip_addresses_to_gateway(GBinding *    binding,
+                        const GValue *source_value,
+                        GValue *      target_value,
+                        gpointer      user_data)
+{
+    GPtrArray *addrs;
+
+    addrs = g_value_get_boxed(source_value);
+    if (addrs->len == 0) {
+        g_value_set_string(target_value, NULL);
+        return TRUE;
+    } else
+        return FALSE;
+}
+
+static gboolean
+ip_addresses_to_sensitivity(GBinding *    binding,
+                            const GValue *source_value,
+                            GValue *      target_value,
+                            gpointer      user_data)
+{
+    GPtrArray *addrs;
+
+    addrs = g_value_get_boxed(source_value);
+    g_value_set_boolean(target_value, addrs->len != 0);
+    return TRUE;
+}
+
+/**
+ * nm_editor_bind_ip_gateway_to_string:
+ * @addr_family: the IP address family
+ * @source: the source #NMSettingIPConfig
+ * @target: the target object (eg, an #NmtIPEntry)
+ * @target_property: the property on @target to bind (eg, "text")
+ * @target_sensitive_property: the "sensitivity" property on @target to bind
+ * @flags: %GBindingFlags
+ *
+ * Binds the #NMSettingIPConfig:gateway property on @source to the
+ * %G_TYPE_STRING property @target_property and %G_TYPE_BOOLEAN property
+ * @target_sensitive_property on @target, also taking the
+ * #NMSettingIPConfig:addresses property on @source into account.
+ *
+ * In particular, if @source has no static IP addresses, then @target_property
+ * will be set to "" and @target_sensitive_property will be set to %FALSE.
+ *
+ * If @source has at least one static IP address, then
+ * @target_sensitive_property will be set to %TRUE, @target_property will be
+ * initialized from @source's #NMSettingIPConfig:gateway, and @source will be
+ * updated with the value of @target_property whenever it contains a valid IP
+ * address.
+ */
+void
+nm_editor_bind_ip_gateway_to_string(int                addr_family,
+                                    NMSettingIPConfig *source,
+                                    gpointer           target,
+                                    const char *       target_property,
+                                    const char *       target_sensitive_property,
+                                    GBindingFlags      flags)
+{
+    g_object_bind_property_full(source,
+                                "gateway",
+                                target,
+                                target_property,
+                                flags,
+                                ip_gateway_to_string,
+                                ip_gateway_from_string,
+                                GINT_TO_POINTER(addr_family),
+                                NULL);
+    g_object_bind_property_full(source,
+                                "addresses",
+                                source,
+                                "gateway",
+                                (flags & G_BINDING_SYNC_CREATE),
+                                ip_addresses_to_gateway,
+                                NULL,
+                                NULL,
+                                NULL);
+    g_object_bind_property_full(source,
+                                "addresses",
+                                target,
+                                target_sensitive_property,
+                                (flags & G_BINDING_SYNC_CREATE),
+                                ip_addresses_to_sensitivity,
+                                NULL,
+                                NULL,
+                                NULL);
+}
+
+static gboolean
+ip_route_transform_to_dest_string(GBinding *    binding,
+                                  const GValue *source_value,
+                                  GValue *      target_value,
+                                  gpointer      user_data)
+{
+    NMIPRoute * route;
+    const char *addrstr;
+    char *      string;
+
+    route = g_value_get_boxed(source_value);
+    if (route)
+        addrstr = nm_ip_route_get_dest(route);
+    else
+        addrstr = NULL;
+
+    if (addrstr) {
+        string = g_strdup_printf("%s/%d", addrstr, (int) nm_ip_route_get_prefix(route));
+        g_value_take_string(target_value, string);
+    } else
+        g_value_set_string(target_value, "");
+    return TRUE;
+}
+
+static gboolean
+ip_route_transform_to_next_hop_string(GBinding *    binding,
+                                      const GValue *source_value,
+                                      GValue *      target_value,
+                                      gpointer      user_data)
+{
+    NMIPRoute * route;
+    const char *addrstr;
+
+    route = g_value_get_boxed(source_value);
+    if (route) {
+        addrstr = nm_ip_route_get_next_hop(route);
+        if (!addrstr)
+            addrstr = "";
+    } else
+        addrstr = "";
+
+    g_value_set_string(target_value, addrstr);
+    return TRUE;
+}
+
+static gboolean
+ip_route_transform_to_metric_string(GBinding *    binding,
+                                    const GValue *source_value,
+                                    GValue *      target_value,
+                                    gpointer      user_data)
+{
+    NMIPRoute *route;
+    char *     string;
+
+    route = g_value_get_boxed(source_value);
+    if (route && nm_ip_route_get_dest(route) && nm_ip_route_get_metric(route) != -1) {
+        string = g_strdup_printf("%lu", (gulong) nm_ip_route_get_metric(route));
+        g_value_take_string(target_value, string);
+    } else
+        g_value_set_string(target_value, "");
+    return TRUE;
+}
+
+static gboolean
+ip_route_transform_from_dest_string(GBinding *    binding,
+                                    const GValue *source_value,
+                                    GValue *      target_value,
+                                    gpointer      user_data)
+{
+    int         addr_family = GPOINTER_TO_INT(user_data);
+    NMIPRoute * route;
+    const char *text;
+    char *      addrstr;
+    int         prefix;
+
+    text = g_value_get_string(source_value);
+    if (!nm_utils_parse_inaddr_prefix(addr_family, text, &addrstr, &prefix))
+        return FALSE;
+
+    /* Fetch the original property value */
+    g_object_get(g_binding_get_source(binding),
+                 g_binding_get_source_property(binding),
+                 &route,
+                 NULL);
+
+    if (prefix == -1) {
+        if (addr_family == AF_INET) {
+            in_addr_t v4;
+
+            inet_pton(addr_family, addrstr, &v4);
+            if (nm_utils_ip_is_site_local(AF_INET, &v4)) {
+                prefix = nm_utils_ip4_get_default_prefix(v4);
+                if (v4 & (~_nm_utils_ip4_prefix_to_netmask(prefix)))
+                    prefix = 32;
+            } else
+                prefix = 32;
+        } else
+            prefix = 64;
+    }
+
+    nm_ip_route_set_dest(route, addrstr);
+    nm_ip_route_set_prefix(route, prefix);
+    g_free(addrstr);
+
+    g_value_take_boxed(target_value, route);
+    return TRUE;
+}
+
+static gboolean
+ip_route_transform_from_next_hop_string(GBinding *    binding,
+                                        const GValue *source_value,
+                                        GValue *      target_value,
+                                        gpointer      user_data)
+{
+    int         addr_family = GPOINTER_TO_INT(user_data);
+    NMIPRoute * route;
+    const char *text;
+
+    text = g_value_get_string(source_value);
+    if (*text) {
+        if (!nm_utils_ipaddr_is_valid(addr_family, text))
+            return FALSE;
+    } else
+        text = NULL;
+
+    /* Fetch the original property value */
+    g_object_get(g_binding_get_source(binding),
+                 g_binding_get_source_property(binding),
+                 &route,
+                 NULL);
+
+    nm_ip_route_set_next_hop(route, text);
+
+    g_value_take_boxed(target_value, route);
+    return TRUE;
+}
+
+static gboolean
+ip_route_transform_from_metric_string(GBinding *    binding,
+                                      const GValue *source_value,
+                                      GValue *      target_value,
+                                      gpointer      user_data)
+{
+    NMIPRoute * route;
+    const char *text;
+    gint64      metric;
+
+    text   = g_value_get_string(source_value);
+    metric = _nm_utils_ascii_str_to_int64(text, 10, 0, G_MAXUINT32, -1);
+
+    /* Fetch the original property value */
+    g_object_get(g_binding_get_source(binding),
+                 g_binding_get_source_property(binding),
+                 &route,
+                 NULL);
+
+    nm_ip_route_set_metric(route, metric);
+
+    g_value_take_boxed(target_value, route);
+    return TRUE;
+}
+
+/**
+ * nm_editor_bind_ip_route_to_strings:
+ * @addr_family: the IP address family
+ * @source: the source object
+ * @source_property: the source property
+ * @dest_target: the target object for the route's destination
+ * @dest_target_property: the property on @dest_target
+ * @next_hop_target: the target object for the route's next hop
+ * @next_hop_target_property: the property on @next_hop_target
+ * @metric_target: the target object for the route's metric
+ * @metric_target_property: the property on @metric_target
+ * @flags: %GBindingFlags
+ *
+ * Binds the #NMIPRoute-valued property @source_property on @source to the
+ * three indicated string-valued target properties (and vice versa if
+ * %G_BINDING_BIDIRECTIONAL is specified).
+ *
+ * @dest_target_property should be an "address/prefix" string, as with
+ * nm_editor_bind_ip4_addresses_with_prefix_to_strv(). @next_hop_target_property
+ * is a plain IP address, and @metric_target_property is a number.
+ */
+void
+nm_editor_bind_ip_route_to_strings(int           addr_family,
+                                   gpointer      source,
+                                   const char *  source_property,
+                                   gpointer      dest_target,
+                                   const char *  dest_target_property,
+                                   gpointer      next_hop_target,
+                                   const char *  next_hop_target_property,
+                                   gpointer      metric_target,
+                                   const char *  metric_target_property,
+                                   GBindingFlags flags)
+{
+    g_object_bind_property_full(source,
+                                source_property,
+                                dest_target,
+                                dest_target_property,
+                                flags,
+                                ip_route_transform_to_dest_string,
+                                ip_route_transform_from_dest_string,
+                                GINT_TO_POINTER(addr_family),
+                                NULL);
+    g_object_bind_property_full(source,
+                                source_property,
+                                next_hop_target,
+                                next_hop_target_property,
+                                flags,
+                                ip_route_transform_to_next_hop_string,
+                                ip_route_transform_from_next_hop_string,
+                                GINT_TO_POINTER(addr_family),
+                                NULL);
+    g_object_bind_property_full(source,
+                                source_property,
+                                metric_target,
+                                metric_target_property,
+                                flags,
+                                ip_route_transform_to_metric_string,
+                                ip_route_transform_from_metric_string,
+                                GINT_TO_POINTER(addr_family),
+                                NULL);
+}
+
+/* Wireless security method binding */
+typedef struct {
+    NMConnection *             connection;
+    NMSettingWirelessSecurity *s_wsec;
+    gboolean                   s_wsec_in_use;
+
+    GObject *target;
+    char *   target_property;
+
+    gboolean updating;
+} NMEditorWirelessSecurityMethodBinding;
+
+static const char *
+get_security_type(NMEditorWirelessSecurityMethodBinding *binding)
+{
+    const char *key_mgmt, *auth_alg;
+
+    if (!binding->s_wsec_in_use)
+        return "none";
+
+    key_mgmt = nm_setting_wireless_security_get_key_mgmt(binding->s_wsec);
+    auth_alg = nm_setting_wireless_security_get_auth_alg(binding->s_wsec);
+
+    /* No IEEE 802.1x */
+    if (!strcmp(key_mgmt, "none")) {
+        NMWepKeyType wep_type = nm_setting_wireless_security_get_wep_key_type(binding->s_wsec);
+
+        if (wep_type == NM_WEP_KEY_TYPE_KEY)
+            return "wep-key";
+        else
+            return "wep-passphrase";
+    }
+
+    if (!strcmp(key_mgmt, "ieee8021x")) {
+        if (auth_alg && !strcmp(auth_alg, "leap"))
+            return "leap";
+        return "dynamic-wep";
+    }
+
+    if (!strcmp(key_mgmt, "wpa-psk"))
+        return "wpa-personal";
+
+    if (!strcmp(key_mgmt, "sae"))
+        return "wpa3-personal";
+
+    if (!strcmp(key_mgmt, "owe"))
+        return "owe";
+
+    if (!strcmp(key_mgmt, "wpa-eap"))
+        return "wpa-enterprise";
+
+    if (!strcmp(key_mgmt, "wpa-eap-suite-b-192"))
+        return "wpa3-enterprise-suite-b-192";
+
+    return NULL;
+}
+
+static void
+wireless_security_changed(GObject *object, GParamSpec *pspec, gpointer user_data)
+{
+    NMEditorWirelessSecurityMethodBinding *binding = user_data;
+
+    if (binding->updating)
+        return;
+
+    binding->updating = TRUE;
+    g_object_set(binding->target, binding->target_property, get_security_type(binding), NULL);
+    binding->updating = FALSE;
+}
+
+static void
+wireless_connection_changed(NMConnection *connection, gpointer user_data)
+{
+    NMEditorWirelessSecurityMethodBinding *binding = user_data;
+    NMSettingWirelessSecurity *            s_wsec;
+
+    if (binding->updating)
+        return;
+
+    s_wsec = nm_connection_get_setting_wireless_security(connection);
+    if ((s_wsec && binding->s_wsec_in_use) || (!s_wsec && !binding->s_wsec_in_use))
+        return;
+
+    binding->s_wsec_in_use = !binding->s_wsec_in_use;
+    wireless_security_changed(NULL, NULL, binding);
+}
+
+static void
+wireless_security_target_changed(GObject *object, GParamSpec *pspec, gpointer user_data)
+{
+    NMEditorWirelessSecurityMethodBinding *binding = user_data;
+    char *                                 method;
+
+    if (binding->updating)
+        return;
+
+    g_object_get(binding->target, binding->target_property, &method, NULL);
+
+    binding->updating = TRUE;
+
+    if (!strcmp(method, "none")) {
+        if (!binding->s_wsec_in_use)
+            return;
+        binding->s_wsec_in_use = FALSE;
+        nm_connection_remove_setting(binding->connection, NM_TYPE_SETTING_WIRELESS_SECURITY);
+
+        binding->updating = FALSE;
+        return;
+    }
+
+    if (!binding->s_wsec_in_use) {
+        binding->s_wsec_in_use = TRUE;
+        nm_connection_add_setting(binding->connection, NM_SETTING(binding->s_wsec));
+    }
+
+    if (!strcmp(method, "wep-key")) {
+        g_object_set(binding->s_wsec,
+                     NM_SETTING_WIRELESS_SECURITY_KEY_MGMT,
+                     "none",
+                     NM_SETTING_WIRELESS_SECURITY_AUTH_ALG,
+                     "open",
+                     NM_SETTING_WIRELESS_SECURITY_WEP_KEY_TYPE,
+                     NM_WEP_KEY_TYPE_KEY,
+                     NULL);
+    } else if (!strcmp(method, "wep-passphrase")) {
+        g_object_set(binding->s_wsec,
+                     NM_SETTING_WIRELESS_SECURITY_KEY_MGMT,
+                     "none",
+                     NM_SETTING_WIRELESS_SECURITY_AUTH_ALG,
+                     "open",
+                     NM_SETTING_WIRELESS_SECURITY_WEP_KEY_TYPE,
+                     NM_WEP_KEY_TYPE_PASSPHRASE,
+                     NULL);
+    } else if (!strcmp(method, "leap")) {
+        g_object_set(binding->s_wsec,
+                     NM_SETTING_WIRELESS_SECURITY_KEY_MGMT,
+                     "ieee8021x",
+                     NM_SETTING_WIRELESS_SECURITY_AUTH_ALG,
+                     "leap",
+                     NM_SETTING_WIRELESS_SECURITY_WEP_KEY_TYPE,
+                     NM_WEP_KEY_TYPE_UNKNOWN,
+                     NULL);
+    } else if (!strcmp(method, "dynamic-wep")) {
+        g_object_set(binding->s_wsec,
+                     NM_SETTING_WIRELESS_SECURITY_KEY_MGMT,
+                     "ieee8021x",
+                     NM_SETTING_WIRELESS_SECURITY_AUTH_ALG,
+                     "open",
+                     NM_SETTING_WIRELESS_SECURITY_WEP_KEY_TYPE,
+                     NM_WEP_KEY_TYPE_UNKNOWN,
+                     NULL);
+    } else if (!strcmp(method, "wpa-personal")) {
+        g_object_set(binding->s_wsec,
+                     NM_SETTING_WIRELESS_SECURITY_KEY_MGMT,
+                     "wpa-psk",
+                     NM_SETTING_WIRELESS_SECURITY_AUTH_ALG,
+                     NULL,
+                     NM_SETTING_WIRELESS_SECURITY_WEP_KEY_TYPE,
+                     NM_WEP_KEY_TYPE_UNKNOWN,
+                     NULL);
+    } else if (!strcmp(method, "wpa3-personal")) {
+        g_object_set(binding->s_wsec,
+                     NM_SETTING_WIRELESS_SECURITY_KEY_MGMT,
+                     "sae",
+                     NM_SETTING_WIRELESS_SECURITY_AUTH_ALG,
+                     NULL,
+                     NM_SETTING_WIRELESS_SECURITY_WEP_KEY_TYPE,
+                     NM_WEP_KEY_TYPE_UNKNOWN,
+                     NULL);
+    } else if (!strcmp(method, "owe")) {
+        g_object_set(binding->s_wsec,
+                     NM_SETTING_WIRELESS_SECURITY_KEY_MGMT,
+                     "owe",
+                     NM_SETTING_WIRELESS_SECURITY_AUTH_ALG,
+                     NULL,
+                     NM_SETTING_WIRELESS_SECURITY_WEP_KEY_TYPE,
+                     NM_WEP_KEY_TYPE_UNKNOWN,
+                     NULL);
+    } else if (!strcmp(method, "wpa-enterprise")) {
+        g_object_set(binding->s_wsec,
+                     NM_SETTING_WIRELESS_SECURITY_KEY_MGMT,
+                     "wpa-eap",
+                     NM_SETTING_WIRELESS_SECURITY_AUTH_ALG,
+                     NULL,
+                     NM_SETTING_WIRELESS_SECURITY_WEP_KEY_TYPE,
+                     NM_WEP_KEY_TYPE_UNKNOWN,
+                     NULL);
+    } else
+        g_warn_if_reached();
+
+    binding->updating = FALSE;
+}
+
+static void
+wireless_security_target_destroyed(gpointer user_data, GObject *ex_target)
+{
+    NMEditorWirelessSecurityMethodBinding *binding = user_data;
+
+    g_signal_handlers_disconnect_by_func(binding->s_wsec,
+                                         G_CALLBACK(wireless_security_changed),
+                                         binding);
+    g_object_unref(binding->s_wsec);
+    g_object_unref(binding->connection);
+
+    g_free(binding->target_property);
+
+    g_slice_free(NMEditorWirelessSecurityMethodBinding, binding);
+}
+
+/**
+ * nm_editor_bind_wireless_security_method:
+ * @connection: an #NMConnection
+ * @s_wsec: an #NMSettingWirelessSecurity
+ * @target: the target widget
+ * @target_property: the string-valued property on @target to bind
+ * @flags: %GBindingFlags
+ *
+ * Binds the wireless security method on @connection to
+ * @target_property on @target (and vice versa if
+ * %G_BINDING_BIDIRECTIONAL).
+ *
+ * @target_property will be of the values "none", "wpa-personal",
+ * "wpa-enterprise", "wep-key", "wep-passphrase", "dynamic-wep", or
+ * "leap".
+ *
+ * If binding bidirectionally, @s_wsec will be automatically added to
+ * or removed from @connection as needed when @target_property
+ * changes.
+ */
+void
+nm_editor_bind_wireless_security_method(NMConnection *             connection,
+                                        NMSettingWirelessSecurity *s_wsec,
+                                        gpointer                   target,
+                                        const char *               target_property,
+                                        GBindingFlags              flags)
+{
+    NMEditorWirelessSecurityMethodBinding *binding;
+    char *                                 notify;
+
+    binding = g_slice_new0(NMEditorWirelessSecurityMethodBinding);
+
+    binding->target          = target;
+    binding->target_property = g_strdup(target_property);
+    if (flags & G_BINDING_BIDIRECTIONAL) {
+        notify = g_strdup_printf("notify::%s", target_property);
+        g_signal_connect(target, notify, G_CALLBACK(wireless_security_target_changed), binding);
+        g_free(notify);
+    }
+    g_object_weak_ref(target, wireless_security_target_destroyed, binding);
+
+    binding->connection = g_object_ref(connection);
+    g_signal_connect(connection,
+                     NM_CONNECTION_CHANGED,
+                     G_CALLBACK(wireless_connection_changed),
+                     binding);
+    binding->s_wsec_in_use = (nm_connection_get_setting_wireless_security(connection) != NULL);
+
+    binding->s_wsec = g_object_ref(s_wsec);
+    g_signal_connect(s_wsec,
+                     "notify::" NM_SETTING_WIRELESS_SECURITY_KEY_MGMT,
+                     G_CALLBACK(wireless_security_changed),
+                     binding);
+    g_signal_connect(s_wsec,
+                     "notify::" NM_SETTING_WIRELESS_SECURITY_AUTH_ALG,
+                     G_CALLBACK(wireless_security_changed),
+                     binding);
+    g_signal_connect(s_wsec,
+                     "notify::" NM_SETTING_WIRELESS_SECURITY_WEP_KEY_TYPE,
+                     G_CALLBACK(wireless_security_changed),
+                     binding);
+
+    if (flags & G_BINDING_SYNC_CREATE)
+        wireless_security_changed(NULL, NULL, binding);
+}
+
+/* WEP key binding */
+
+typedef struct {
+    NMSettingWirelessSecurity *s_wsec;
+    GObject *                  entry, *key_selector;
+    char *                     entry_property, *key_selector_property;
+
+    gboolean updating;
+} NMEditorWepKeyBinding;
+
+static void
+wep_key_setting_changed(GObject *object, GParamSpec *pspec, gpointer user_data)
+{
+    NMEditorWepKeyBinding *binding = user_data;
+    const char *           key;
+    int                    index;
+
+    if (binding->updating)
+        return;
+
+    index = nm_setting_wireless_security_get_wep_tx_keyidx(binding->s_wsec);
+    key   = nm_setting_wireless_security_get_wep_key(binding->s_wsec, index);
+
+    binding->updating = TRUE;
+    g_object_set(binding->key_selector, binding->key_selector_property, index, NULL);
+    g_object_set(binding->entry, binding->entry_property, key, NULL);
+    binding->updating = FALSE;
+}
+
+static void
+wep_key_ui_changed(GObject *object, GParamSpec *pspec, gpointer user_data)
+{
+    NMEditorWepKeyBinding *binding = user_data;
+    char *                 key;
+    int                    index;
+
+    if (binding->updating)
+        return;
+
+    g_object_get(binding->key_selector, binding->key_selector_property, &index, NULL);
+    g_object_get(binding->entry, binding->entry_property, &key, NULL);
+
+    binding->updating = TRUE;
+    g_object_set(binding->s_wsec,
+                 NM_SETTING_WIRELESS_SECURITY_WEP_TX_KEYIDX,
+                 index,
+                 NM_SETTING_WIRELESS_SECURITY_WEP_KEY0,
+                 index == 0 ? key : NULL,
+                 NM_SETTING_WIRELESS_SECURITY_WEP_KEY1,
+                 index == 1 ? key : NULL,
+                 NM_SETTING_WIRELESS_SECURITY_WEP_KEY2,
+                 index == 2 ? key : NULL,
+                 NM_SETTING_WIRELESS_SECURITY_WEP_KEY3,
+                 index == 3 ? key : NULL,
+                 NULL);
+    binding->updating = FALSE;
+
+    g_free(key);
+}
+
+static void
+wep_key_target_destroyed(gpointer user_data, GObject *ex_target)
+{
+    NMEditorWepKeyBinding *binding = user_data;
+
+    g_signal_handlers_disconnect_by_func(binding->s_wsec,
+                                         G_CALLBACK(wep_key_setting_changed),
+                                         binding);
+
+    if (ex_target != binding->entry) {
+        g_signal_handlers_disconnect_by_func(binding->entry,
+                                             G_CALLBACK(wep_key_ui_changed),
+                                             binding);
+        g_object_weak_unref(binding->entry, wep_key_target_destroyed, binding);
+    } else {
+        g_signal_handlers_disconnect_by_func(binding->key_selector,
+                                             G_CALLBACK(wep_key_ui_changed),
+                                             binding);
+        g_object_weak_unref(binding->key_selector, wep_key_target_destroyed, binding);
+    }
+
+    g_object_unref(binding->s_wsec);
+    g_free(binding->entry_property);
+    g_free(binding->key_selector_property);
+
+    g_slice_free(NMEditorWepKeyBinding, binding);
+}
+
+/**
+ * nm_editor_bind_wireless_security_wep_key:
+ * @s_wsec: an #NMSettingWirelessSecurity
+ * @entry: an entry widget
+ * @entry_property: the string-valued property on @entry to bind
+ * @key_selector: a pop-up widget of some sort
+ * @key_selector_property: the integer-valued property on
+ *   @key_selector to bind
+ * @flags: %GBindingFlags
+ *
+ * Binds the "wep-tx-keyidx" property on @s_wsec to
+ * @key_selector_property on @key_selector, and the corresponding
+ * "wep-keyN" property to @entry_property on @entry (and vice versa if
+ * %G_BINDING_BIDIRECTIONAL).
+ */
+void
+nm_editor_bind_wireless_security_wep_key(NMSettingWirelessSecurity *s_wsec,
+                                         gpointer                   entry,
+                                         const char *               entry_property,
+                                         gpointer                   key_selector,
+                                         const char *               key_selector_property,
+                                         GBindingFlags              flags)
+{
+    NMEditorWepKeyBinding *binding;
+    char *                 notify;
+
+    binding                        = g_slice_new0(NMEditorWepKeyBinding);
+    binding->s_wsec                = g_object_ref(s_wsec);
+    binding->entry                 = entry;
+    binding->entry_property        = g_strdup(entry_property);
+    binding->key_selector          = key_selector;
+    binding->key_selector_property = g_strdup(key_selector_property);
+
+    g_signal_connect(s_wsec,
+                     "notify::" NM_SETTING_WIRELESS_SECURITY_WEP_KEY0,
+                     G_CALLBACK(wep_key_setting_changed),
+                     binding);
+    g_signal_connect(s_wsec,
+                     "notify::" NM_SETTING_WIRELESS_SECURITY_WEP_KEY1,
+                     G_CALLBACK(wep_key_setting_changed),
+                     binding);
+    g_signal_connect(s_wsec,
+                     "notify::" NM_SETTING_WIRELESS_SECURITY_WEP_KEY2,
+                     G_CALLBACK(wep_key_setting_changed),
+                     binding);
+    g_signal_connect(s_wsec,
+                     "notify::" NM_SETTING_WIRELESS_SECURITY_WEP_KEY3,
+                     G_CALLBACK(wep_key_setting_changed),
+                     binding);
+
+    g_signal_connect(s_wsec,
+                     "notify::" NM_SETTING_WIRELESS_SECURITY_WEP_TX_KEYIDX,
+                     G_CALLBACK(wep_key_setting_changed),
+                     binding);
+
+    if (flags & G_BINDING_BIDIRECTIONAL) {
+        notify = g_strdup_printf("notify::%s", entry_property);
+        g_signal_connect(entry, notify, G_CALLBACK(wep_key_ui_changed), binding);
+        g_free(notify);
+
+        notify = g_strdup_printf("notify::%s", key_selector_property);
+        g_signal_connect(key_selector, notify, G_CALLBACK(wep_key_ui_changed), binding);
+        g_free(notify);
+    }
+
+    g_object_weak_ref(entry, wep_key_target_destroyed, binding);
+    g_object_weak_ref(key_selector, wep_key_target_destroyed, binding);
+
+    if (flags & G_BINDING_SYNC_CREATE)
+        wep_key_setting_changed(NULL, NULL, binding);
+}
+
+/* VLAN binding */
+
+typedef struct {
+    NMSettingVlan *      s_vlan;
+    NMSettingConnection *s_con;
+
+    char *last_ifname_parent;
+    int   last_ifname_id;
+
+    gboolean updating;
+} NMEditorVlanWidgetBinding;
+
+static gboolean
+parse_interface_name(const char *ifname, char **parent_ifname, int *id)
+{
+    const char *ifname_end;
+    char *      end;
+
+    if (!ifname || !*ifname)
+        return FALSE;
+
+    if (g_str_has_prefix(ifname, "vlan")) {
+        ifname_end = ifname + 4;
+        *id        = strtoul(ifname_end, &end, 10);
+        if (*end || end == (char *) ifname_end || *id < 0)
+            return FALSE;
+        *parent_ifname = NULL;
+        return TRUE;
+    }
+
+    ifname_end = strchr(ifname, '.');
+    if (ifname_end) {
+        *id = strtoul(ifname_end + 1, &end, 10);
+        if (*end || end == (char *) ifname_end + 1 || *id < 0)
+            return FALSE;
+        *parent_ifname = g_strndup(ifname, ifname_end - ifname);
+        return TRUE;
+    }
+
+    return FALSE;
+}
+
+static void
+vlan_settings_changed(GObject *object, GParamSpec *pspec, gpointer user_data)
+{
+    NMEditorVlanWidgetBinding *binding = user_data;
+    const char *               ifname, *parent;
+    char *                     ifname_parent;
+    int                        ifname_id, id;
+
+    if (binding->updating)
+        return;
+
+    ifname = nm_setting_connection_get_interface_name(binding->s_con);
+    parent = nm_setting_vlan_get_parent(binding->s_vlan);
+    id     = nm_setting_vlan_get_id(binding->s_vlan);
+
+    if (!parse_interface_name(ifname, &ifname_parent, &ifname_id))
+        return;
+
+    /* If the id in INTERFACE_NAME changed, and ID is either unset, or was previously
+     * in sync with INTERFACE_NAME, then update ID.
+     */
+    if (id != ifname_id && (id == binding->last_ifname_id || id == 0)) {
+        binding->updating = TRUE;
+        g_object_set(G_OBJECT(binding->s_vlan), NM_SETTING_VLAN_ID, ifname_id, NULL);
+        binding->updating = FALSE;
+    }
+
+    /* If the PARENT in INTERFACE_NAME changed, and PARENT is either unset, or was
+     * previously in sync with INTERFACE_NAME, then update PARENT.
+     */
+    if (g_strcmp0(parent, ifname_parent) != 0
+        && (g_strcmp0(parent, binding->last_ifname_parent) == 0 || !parent || !*parent)) {
+        binding->updating = TRUE;
+        g_object_set(G_OBJECT(binding->s_vlan), NM_SETTING_VLAN_PARENT, ifname_parent, NULL);
+        binding->updating = FALSE;
+    }
+
+    g_free(binding->last_ifname_parent);
+    binding->last_ifname_parent = ifname_parent;
+    binding->last_ifname_id     = ifname_id;
+}
+
+static void
+vlan_target_destroyed(gpointer user_data, GObject *ex_target)
+{
+    NMEditorVlanWidgetBinding *binding = user_data;
+
+    g_free(binding->last_ifname_parent);
+    g_slice_free(NMEditorVlanWidgetBinding, binding);
+}
+
+/**
+ * nm_editor_bind_vlan_name:
+ * @s_vlan: an #NMSettingVlan
+ *
+ * Binds together several properties on @s_vlan, so that if the
+ * %NM_SETTING_VLAN_INTERFACE_NAME matches %NM_SETTING_VLAN_PARENT
+ * and %NM_SETTING_VLAN_ID in the obvious way, then changes to
+ * %NM_SETTING_VLAN_INTERFACE_NAME will propagate to the other
+ * two properties automatically.
+ */
+void
+nm_editor_bind_vlan_name(NMSettingVlan *s_vlan, NMSettingConnection *s_con)
+{
+    NMEditorVlanWidgetBinding *binding;
+    const char *               ifname;
+
+    binding         = g_slice_new0(NMEditorVlanWidgetBinding);
+    binding->s_vlan = s_vlan;
+    binding->s_con  = s_con;
+
+    g_signal_connect(s_con,
+                     "notify::" NM_SETTING_CONNECTION_INTERFACE_NAME,
+                     G_CALLBACK(vlan_settings_changed),
+                     binding);
+
+    g_object_weak_ref(G_OBJECT(s_vlan), vlan_target_destroyed, binding);
+
+    ifname = nm_setting_connection_get_interface_name(s_con);
+    if (!parse_interface_name(ifname, &binding->last_ifname_parent, &binding->last_ifname_id)) {
+        binding->last_ifname_parent = NULL;
+        binding->last_ifname_id     = 0;
+    }
+}
diff --git a/src/nmtui/nm-editor-bindings.h b/src/nmtui/nm-editor-bindings.h
new file mode 100644
index 00000000..027a59a9
--- /dev/null
+++ b/src/nmtui/nm-editor-bindings.h
@@ -0,0 +1,56 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+#ifndef NM_EDITOR_BINDINGS_H
+#define NM_EDITOR_BINDINGS_H
+
+void nm_editor_bindings_init(void);
+
+void nm_editor_bind_ip_addresses_with_prefix_to_strv(int           family,
+                                                     gpointer      source,
+                                                     const char *  source_property,
+                                                     gpointer      target,
+                                                     const char *  target_property,
+                                                     GBindingFlags flags);
+void nm_editor_bind_ip_addresses_to_strv(int           family,
+                                         gpointer      source,
+                                         const char *  source_property,
+                                         gpointer      target,
+                                         const char *  target_property,
+                                         GBindingFlags flags);
+
+void nm_editor_bind_ip_gateway_to_string(int                family,
+                                         NMSettingIPConfig *source,
+                                         gpointer           target,
+                                         const char *       target_property,
+                                         const char *       target_sensitive_property,
+                                         GBindingFlags      flags);
+
+void nm_editor_bind_ip_route_to_strings(int           family,
+                                        gpointer      source,
+                                        const char *  source_property,
+                                        gpointer      dest_target,
+                                        const char *  dest_target_property,
+                                        gpointer      next_hop_target,
+                                        const char *  next_hop_target_property,
+                                        gpointer      metric_target,
+                                        const char *  metric_target_property,
+                                        GBindingFlags flags);
+
+void nm_editor_bind_wireless_security_method(NMConnection *             connection,
+                                             NMSettingWirelessSecurity *s_wsec,
+                                             gpointer                   target,
+                                             const char *               target_property,
+                                             GBindingFlags              flags);
+void nm_editor_bind_wireless_security_wep_key(NMSettingWirelessSecurity *s_wsec,
+                                              gpointer                   entry,
+                                              const char *               entry_property,
+                                              gpointer                   key_selector,
+                                              const char *               key_selector_property,
+                                              GBindingFlags              flags);
+
+void nm_editor_bind_vlan_name(NMSettingVlan *s_vlan, NMSettingConnection *s_con);
+
+#endif /* NM_EDITOR_BINDINGS_H */
diff --git a/src/nmtui/nm-editor-utils.c b/src/nmtui/nm-editor-utils.c
new file mode 100644
index 00000000..c6b03bd8
--- /dev/null
+++ b/src/nmtui/nm-editor-utils.c
@@ -0,0 +1,467 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2012, 2013 Red Hat, Inc.
+ */
+
+/**
+ * SECTION:nm-editor-utils
+ * @short_description: Miscellaneous connection editor utilities
+ *
+ * nm-editor-utils contains helper functions for connection editors.
+ * The goal is that this should eventually be shared between nmtui,
+ * nm-connection-editor, and gnome-control-center.
+ */
+
+#include "libnm-client-aux-extern/nm-default-client.h"
+
+#include "nm-editor-utils.h"
+#if 0
+    #include "libnmc-base/nm-vpn-helpers.h"
+
+static GSList *vpn_plugins;
+
+static int
+sort_vpn_plugins (gconstpointer a, gconstpointer b)
+{
+    NMVpnEditorPlugin *aa = NM_VPN_EDITOR_PLUGIN (a);
+    NMVpnEditorPlugin *bb = NM_VPN_EDITOR_PLUGIN (b);
+    char *aa_desc = NULL, *bb_desc = NULL;
+    int ret;
+
+    g_object_get (aa, NM_VPN_EDITOR_PLUGIN_NAME, &aa_desc, NULL);
+    g_object_get (bb, NM_VPN_EDITOR_PLUGIN_NAME, &bb_desc, NULL);
+
+    ret = g_strcmp0 (aa_desc, bb_desc);
+
+    g_free (aa_desc);
+    g_free (bb_desc);
+
+    return ret;
+}
+#endif
+
+static void
+wifi_connection_setup_func(NMConnection *connection, NMSettingConnection *s_con, NMSetting *s_hw)
+{
+    g_object_set(G_OBJECT(s_hw), NM_SETTING_WIRELESS_MODE, NM_SETTING_WIRELESS_MODE_INFRA, NULL);
+}
+
+static void
+bond_connection_setup_func(NMConnection *connection, NMSettingConnection *s_con, NMSetting *s_hw)
+{
+    NMSettingBond *          s_bond = NM_SETTING_BOND(s_hw);
+    guint                    i;
+    const char *             value;
+    static const char *const options[] = {
+        NM_SETTING_BOND_OPTION_MODE,
+        NM_SETTING_BOND_OPTION_MIIMON,
+        NM_SETTING_BOND_OPTION_DOWNDELAY,
+        NM_SETTING_BOND_OPTION_UPDELAY,
+    };
+
+    for (i = 0; i < G_N_ELEMENTS(options); i++) {
+        value = nm_setting_bond_get_option_default(s_bond, options[i]);
+        if (value)
+            nm_setting_bond_add_option(s_bond, options[i], value);
+    }
+}
+
+typedef void (*NMEditorNewConnectionSetupFunc)(NMConnection *       connection,
+                                               NMSettingConnection *s_con,
+                                               NMSetting *          s_hw);
+
+typedef struct {
+    NMEditorConnectionTypeData data;
+
+    const char *                   id_format;
+    NMEditorNewConnectionSetupFunc connection_setup_func;
+    gboolean                       no_autoconnect;
+} NMEditorConnectionTypeDataReal;
+
+static int
+sort_types(gconstpointer a, gconstpointer b)
+{
+    NMEditorConnectionTypeData *typea = *(NMEditorConnectionTypeData **) a;
+    NMEditorConnectionTypeData *typeb = *(NMEditorConnectionTypeData **) b;
+
+    if (typea->virtual && !typeb->virtual)
+        return 1;
+    else if (typeb->virtual && !typea->virtual)
+        return -1;
+
+    if (typea->setting_type == NM_TYPE_SETTING_VPN && typeb->setting_type != NM_TYPE_SETTING_VPN)
+        return 1;
+    else if (typeb->setting_type == NM_TYPE_SETTING_VPN
+             && typea->setting_type != NM_TYPE_SETTING_VPN)
+        return -1;
+
+    return g_utf8_collate(typea->name, typeb->name);
+}
+
+/**
+ * nm_editor_utils_get_connection_type_list:
+ *
+ * Gets an array of information about supported connection types. The
+ * array is sorted in a standard presentation order (hardware types
+ * first, alphabetized, then virtual types, alphabetized, then VPN
+ * types, alphabetized).
+ *
+ * Returns: the array of connection type information
+ */
+NMEditorConnectionTypeData **
+nm_editor_utils_get_connection_type_list(void)
+{
+    GPtrArray *                         array;
+    NMEditorConnectionTypeDataReal *    item;
+    static NMEditorConnectionTypeData **list;
+#if 0
+    GHashTable *vpn_plugins_hash;
+    gboolean have_vpn_plugins;
+#endif
+
+    if (list)
+        return list;
+
+    array = g_ptr_array_new();
+
+    item                    = g_new0(NMEditorConnectionTypeDataReal, 1);
+    item->data.name         = _("Ethernet");
+    item->data.setting_type = NM_TYPE_SETTING_WIRED;
+    item->data.device_type  = NM_TYPE_DEVICE_ETHERNET;
+    item->data.virtual      = FALSE;
+    item->id_format         = _("Ethernet connection %d");
+    g_ptr_array_add(array, item);
+
+    item                    = g_new0(NMEditorConnectionTypeDataReal, 1);
+    item->data.name         = _("Veth");
+    item->data.setting_type = NM_TYPE_SETTING_VETH;
+    item->data.device_type  = NM_TYPE_DEVICE_VETH;
+    item->data.virtual      = TRUE;
+    item->id_format         = _("Veth connection %d");
+    g_ptr_array_add(array, item);
+
+    item                        = g_new0(NMEditorConnectionTypeDataReal, 1);
+    item->data.name             = _("Wi-Fi");
+    item->data.setting_type     = NM_TYPE_SETTING_WIRELESS;
+    item->data.device_type      = NM_TYPE_DEVICE_WIFI;
+    item->data.virtual          = FALSE;
+    item->id_format             = _("Wi-Fi connection %d");
+    item->connection_setup_func = wifi_connection_setup_func;
+    g_ptr_array_add(array, item);
+
+    item                    = g_new0(NMEditorConnectionTypeDataReal, 1);
+    item->data.name         = _("InfiniBand");
+    item->data.setting_type = NM_TYPE_SETTING_INFINIBAND;
+    item->data.device_type  = NM_TYPE_DEVICE_INFINIBAND;
+    item->data.virtual      = FALSE;
+    item->id_format         = _("InfiniBand connection %d");
+    g_ptr_array_add(array, item);
+
+#if 0
+    item = g_new0 (NMEditorConnectionTypeDataReal, 1);
+    item->data.name = _("Mobile Broadband");
+    item->data.setting_type = NM_TYPE_SETTING_GSM;
+    item->data.virtual = FALSE;
+    item->id_format = _("Mobile broadband connection %d");
+    item->no_autoconnect = TRUE;
+    g_ptr_array_add (array, item);
+#endif
+
+    item                    = g_new0(NMEditorConnectionTypeDataReal, 1);
+    item->data.name         = _("DSL");
+    item->data.setting_type = NM_TYPE_SETTING_PPPOE;
+    item->data.device_type  = NM_TYPE_DEVICE_ETHERNET;
+    item->data.virtual      = FALSE;
+    item->id_format         = _("DSL connection %d");
+    item->no_autoconnect    = TRUE;
+    g_ptr_array_add(array, item);
+
+    item                        = g_new0(NMEditorConnectionTypeDataReal, 1);
+    item->data.name             = _("Bond");
+    item->data.setting_type     = NM_TYPE_SETTING_BOND;
+    item->data.device_type      = NM_TYPE_DEVICE_BOND;
+    item->data.virtual          = TRUE;
+    item->id_format             = _("Bond connection %d");
+    item->connection_setup_func = bond_connection_setup_func;
+    g_ptr_array_add(array, item);
+
+    item                          = g_new0(NMEditorConnectionTypeDataReal, 1);
+    item->data.name               = _("Bridge");
+    item->data.setting_type       = NM_TYPE_SETTING_BRIDGE;
+    item->data.slave_setting_type = NM_TYPE_SETTING_BRIDGE_PORT;
+    item->data.device_type        = NM_TYPE_DEVICE_BRIDGE;
+    item->data.virtual            = TRUE;
+    item->id_format               = _("Bridge connection %d");
+    g_ptr_array_add(array, item);
+
+    item                          = g_new0(NMEditorConnectionTypeDataReal, 1);
+    item->data.name               = _("Team");
+    item->data.setting_type       = NM_TYPE_SETTING_TEAM;
+    item->data.slave_setting_type = NM_TYPE_SETTING_TEAM_PORT;
+    item->data.device_type        = NM_TYPE_DEVICE_TEAM;
+    item->data.virtual            = TRUE;
+    item->id_format               = _("Team connection %d");
+    g_ptr_array_add(array, item);
+
+    item                    = g_new0(NMEditorConnectionTypeDataReal, 1);
+    item->data.name         = _("VLAN");
+    item->data.setting_type = NM_TYPE_SETTING_VLAN;
+    item->data.device_type  = NM_TYPE_DEVICE_VLAN;
+    item->data.virtual      = TRUE;
+    item->id_format         = _("VLAN connection %d");
+    g_ptr_array_add(array, item);
+
+    item                    = g_new0(NMEditorConnectionTypeDataReal, 1);
+    item->data.name         = _("IP tunnel");
+    item->data.setting_type = NM_TYPE_SETTING_IP_TUNNEL;
+    item->data.device_type  = NM_TYPE_DEVICE_IP_TUNNEL;
+    item->data.virtual      = TRUE;
+    item->id_format         = _("IP tunnel connection %d");
+    g_ptr_array_add(array, item);
+
+#if 0
+    /* Add "VPN" only if there are plugins */
+    vpn_plugins_hash = nm_vpn_get_plugin_infos ();
+    have_vpn_plugins  = vpn_plugins_hash && g_hash_table_size (vpn_plugins_hash);
+    if (have_vpn_plugins) {
+        GHashTableIter iter;
+        gpointer name, plugin;
+
+        item = g_new0 (NMEditorConnectionTypeDataReal, 1);
+        item->data.name = _("VPN");
+        item->data.setting_type = NM_TYPE_SETTING_VPN;
+        item->data.virtual = TRUE;
+        item->id_format = _("VPN connection %d");
+        item->no_autoconnect = TRUE;
+        g_ptr_array_add (array, item);
+
+        vpn_plugins = NULL;
+        g_hash_table_iter_init (&iter, vpn_plugins_hash);
+        while (g_hash_table_iter_next (&iter, &name, &plugin))
+            vpn_plugins = g_slist_prepend (vpn_plugins, plugin);
+        vpn_plugins = g_slist_sort (vpn_plugins, sort_vpn_plugins);
+    }
+#endif
+
+    g_ptr_array_sort(array, sort_types);
+    g_ptr_array_add(array, NULL);
+
+    list = (NMEditorConnectionTypeData **) g_ptr_array_free(array, FALSE);
+    return list;
+}
+
+static gboolean
+_assert_format_int(const char *format)
+{
+    g_assert(format);
+    format = strchr(format, '%');
+    g_assert(format);
+    format++;
+    g_assert(!strchr(format, '%'));
+    g_assert(format[0] == 'd');
+    return TRUE;
+}
+
+static char *
+get_available_connection_name(const char *format, NMClient *client)
+{
+    const GPtrArray *conns;
+    GSList *         names = NULL, *iter;
+    char *           cname = NULL;
+    int              i     = 0;
+
+    nm_assert(_assert_format_int(format));
+
+    conns = nm_client_get_connections(client);
+    for (i = 0; i < conns->len; i++) {
+        const char *id;
+
+        id = nm_connection_get_id(NM_CONNECTION(conns->pdata[i]));
+        g_assert(id);
+        names = g_slist_append(names, (gpointer) id);
+    }
+
+    /* Find the next available unique connection name */
+    for (i = 1; !cname && i < 10000; i++) {
+        char *   temp;
+        gboolean found = FALSE;
+
+        NM_PRAGMA_WARNING_DISABLE("-Wformat-nonliteral")
+        temp = g_strdup_printf(format, i);
+        NM_PRAGMA_WARNING_REENABLE
+        for (iter = names; iter; iter = g_slist_next(iter)) {
+            if (!strcmp(iter->data, temp)) {
+                found = TRUE;
+                break;
+            }
+        }
+        if (!found)
+            cname = temp;
+        else
+            g_free(temp);
+    }
+
+    g_slist_free(names);
+    return cname;
+}
+
+static char *
+get_available_iface_name(const char *try_name, NMClient *client)
+{
+    const GPtrArray *connections;
+    NMConnection *   connection;
+    char *           new_name;
+    unsigned         num    = 1;
+    int              i      = 0;
+    const char *     ifname = NULL;
+
+    connections = nm_client_get_connections(client);
+
+    new_name = g_strdup(try_name);
+    while (i < connections->len) {
+        connection = NM_CONNECTION(connections->pdata[i]);
+        ifname     = nm_connection_get_interface_name(connection);
+        if (g_strcmp0(new_name, ifname) == 0) {
+            g_free(new_name);
+            new_name = g_strdup_printf("%s%d", try_name, num++);
+            i        = 0;
+        } else
+            i++;
+    }
+    return new_name;
+}
+
+/**
+ * nm_editor_utils_create_connection:
+ * @type: the type of the connection's primary #NMSetting
+ * @master: (allow-none): the connection's master, if any
+ * @client: an #NMClient
+ *
+ * Creates a new #NMConnection of the given type, automatically
+ * creating a UUID and an appropriate not-currently-in-use connection
+ * name, setting #NMSettingConnection:autoconnect appropriately for
+ * the connection type, filling in slave-related information if
+ * @master is not %NULL, and initializing any other mandatory-to-set
+ * properties to reasonable initial values.
+ *
+ * Returns: a new #NMConnection
+ */
+NMConnection *
+nm_editor_utils_create_connection(GType type, NMConnection *master, NMClient *client)
+{
+    NMEditorConnectionTypeData **   types;
+    NMEditorConnectionTypeDataReal *type_data           = NULL;
+    const char *                    master_setting_type = NULL, *master_uuid = NULL;
+    GType                master_type = G_TYPE_INVALID, slave_setting_type = G_TYPE_INVALID;
+    NMConnection *       connection;
+    NMSettingConnection *s_con;
+    NMSetting *          s_hw, *s_slave;
+    char *               uuid, *id, *ifname;
+    int                  i;
+
+    if (master) {
+        NMSettingConnection *master_s_con;
+
+        master_s_con        = nm_connection_get_setting_connection(master);
+        master_setting_type = nm_setting_connection_get_connection_type(master_s_con);
+        master_uuid         = nm_setting_connection_get_uuid(master_s_con);
+        master_type         = nm_setting_lookup_type(master_setting_type);
+    }
+
+    types = nm_editor_utils_get_connection_type_list();
+    for (i = 0; types[i]; i++) {
+        if (types[i]->setting_type == type)
+            type_data = (NMEditorConnectionTypeDataReal *) types[i];
+        if (types[i]->setting_type == master_type)
+            slave_setting_type = types[i]->slave_setting_type;
+    }
+    if (!type_data) {
+        g_return_val_if_reached(NULL);
+        return NULL;
+    }
+
+    connection = nm_simple_connection_new();
+
+    s_con = NM_SETTING_CONNECTION(nm_setting_connection_new());
+    nm_connection_add_setting(connection, NM_SETTING(s_con));
+
+    s_hw = g_object_new(type, NULL);
+    nm_connection_add_setting(connection, s_hw);
+
+    if (type == NM_TYPE_SETTING_BOND)
+        ifname = get_available_iface_name("nm-bond", client);
+    else if (type == NM_TYPE_SETTING_TEAM)
+        ifname = get_available_iface_name("nm-team", client);
+    else if (type == NM_TYPE_SETTING_BRIDGE)
+        ifname = get_available_iface_name("nm-bridge", client);
+    else
+        ifname = NULL;
+
+    if (slave_setting_type != G_TYPE_INVALID) {
+        s_slave = g_object_new(slave_setting_type, NULL);
+        nm_connection_add_setting(connection, s_slave);
+    }
+
+    uuid = nm_utils_uuid_generate();
+    id   = get_available_connection_name(type_data->id_format, client);
+
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_UUID,
+                 uuid,
+                 NM_SETTING_CONNECTION_ID,
+                 id,
+                 NM_SETTING_CONNECTION_TYPE,
+                 nm_setting_get_name(s_hw),
+                 NM_SETTING_CONNECTION_AUTOCONNECT,
+                 !type_data->no_autoconnect,
+                 NM_SETTING_CONNECTION_MASTER,
+                 master_uuid,
+                 NM_SETTING_CONNECTION_SLAVE_TYPE,
+                 master_setting_type,
+                 NM_SETTING_CONNECTION_INTERFACE_NAME,
+                 ifname,
+                 NULL);
+
+    g_free(uuid);
+    g_free(id);
+    g_free(ifname);
+
+    if (type_data->connection_setup_func)
+        type_data->connection_setup_func(connection, s_con, s_hw);
+
+    return connection;
+}
+
+/**
+ * nm_editor_utils_get_connection_type_data:
+ * @conn: an #NMConnection
+ *
+ * Gets the #NMEditorConnectionTypeData corresponding to
+ * @conn's connection type.
+ *
+ * Returns: the #NMEditorConnectionTypeData
+ */
+NMEditorConnectionTypeData *
+nm_editor_utils_get_connection_type_data(NMConnection *conn)
+{
+    NMSettingConnection *        s_con;
+    const char *                 conn_type;
+    GType                        conn_gtype;
+    NMEditorConnectionTypeData **types;
+    int                          i;
+
+    s_con = nm_connection_get_setting_connection(conn);
+    g_return_val_if_fail(s_con != NULL, NULL);
+
+    conn_type  = nm_setting_connection_get_connection_type(s_con);
+    conn_gtype = nm_setting_lookup_type(conn_type);
+    g_return_val_if_fail(conn_gtype != G_TYPE_INVALID, NULL);
+
+    types = nm_editor_utils_get_connection_type_list();
+    for (i = 0; types[i]; i++) {
+        if (types[i]->setting_type == conn_gtype)
+            return types[i];
+    }
+
+    return NULL;
+}
diff --git a/src/nmtui/nm-editor-utils.h b/src/nmtui/nm-editor-utils.h
new file mode 100644
index 00000000..5b624f93
--- /dev/null
+++ b/src/nmtui/nm-editor-utils.h
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2012, 2013 Red Hat, Inc.
+ */
+
+#ifndef NM_EDITOR_UTILS_H
+#define NM_EDITOR_UTILS_H
+
+typedef struct {
+    const char *name;
+    GType       setting_type;
+    GType       slave_setting_type;
+    GType       device_type;
+    gboolean virtual;
+} NMEditorConnectionTypeData;
+
+NMEditorConnectionTypeData **nm_editor_utils_get_connection_type_list(void);
+NMEditorConnectionTypeData * nm_editor_utils_get_connection_type_data(NMConnection *conn);
+
+NMConnection *nm_editor_utils_create_connection(GType type, NMConnection *master, NMClient *client);
+
+#endif /* NM_EDITOR_UTILS_H */
diff --git a/src/nmtui/nmt-address-list.c b/src/nmtui/nmt-address-list.c
new file mode 100644
index 00000000..265457d6
--- /dev/null
+++ b/src/nmtui/nmt-address-list.c
@@ -0,0 +1,266 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+/**
+ * SECTION:nmt-address-list
+ * @short_description: An editable list of IP addresses or hostnames
+ *
+ * #NmtAddressList is a subclass of #NmtWidgetList that contains
+ * entries displaying IP addresses, address/prefix strings, or
+ * hostnames. This is designed for binding its #NmtAddressList:strings
+ * property to an appropriate #NMSettingIP4Config or
+ * #NMSettingIP6Config property via one of the nm-editor-bindings
+ * functions.
+ */
+
+#include "libnm-client-aux-extern/nm-default-client.h"
+
+#include "nmt-address-list.h"
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <stdlib.h>
+
+#include "nmt-ip-entry.h"
+
+G_DEFINE_TYPE(NmtAddressList, nmt_address_list, NMT_TYPE_WIDGET_LIST)
+
+#define NMT_ADDRESS_LIST_GET_PRIVATE(o) \
+    (G_TYPE_INSTANCE_GET_PRIVATE((o), NMT_TYPE_ADDRESS_LIST, NmtAddressListPrivate))
+
+typedef struct {
+    NmtAddressListType list_type;
+    char **            strings;
+} NmtAddressListPrivate;
+
+enum {
+    PROP_0,
+    PROP_LIST_TYPE,
+    PROP_STRINGS,
+
+    LAST_PROP
+};
+
+/**
+ * NmtAddressListType:
+ * @NMT_ADDRESS_LIST_IP4_WITH_PREFIX: IPv4 address/prefix strings
+ * @NMT_ADDRESS_LIST_IP4: IPv4 addresses
+ * @NMT_ADDRESS_LIST_IP6_WITH_PREFIX: IPv6 address/prefix strings
+ * @NMT_ADDRESS_LIST_IP6: IPv6 addresses
+ * @NMT_ADDRESS_LIST_HOSTNAME: hostnames
+ *
+ * The type of address in an #NmtAddressList
+ */
+
+/**
+ * nmt_address_list_new:
+ * @list_type: the type of address the list will contain
+ *
+ * Creates a new #NmtAddressList
+ *
+ * Returns: a new #NmtAddressList
+ */
+NmtNewtWidget *
+nmt_address_list_new(NmtAddressListType list_type)
+{
+    return g_object_new(NMT_TYPE_ADDRESS_LIST, "list-type", list_type, NULL);
+}
+
+static void
+nmt_address_list_init(NmtAddressList *list)
+{}
+
+static gboolean
+strings_transform_to_entry(GBinding *    binding,
+                           const GValue *source_value,
+                           GValue *      target_value,
+                           gpointer      user_data)
+{
+    int    n = GPOINTER_TO_INT(user_data);
+    char **strings;
+
+    strings = g_value_get_boxed(source_value);
+    if (n >= g_strv_length(strings))
+        return FALSE;
+
+    g_value_set_string(target_value, strings[n]);
+    return TRUE;
+}
+
+static gboolean
+strings_transform_from_entry(GBinding *    binding,
+                             const GValue *source_value,
+                             GValue *      target_value,
+                             gpointer      user_data)
+{
+    NmtAddressList *       list = NMT_ADDRESS_LIST(g_binding_get_source(binding));
+    NmtAddressListPrivate *priv = NMT_ADDRESS_LIST_GET_PRIVATE(list);
+    int                    n    = GPOINTER_TO_INT(user_data);
+
+    if (n >= g_strv_length(priv->strings))
+        return FALSE;
+
+    g_free(priv->strings[n]);
+    priv->strings[n] = g_value_dup_string(source_value);
+
+    g_value_set_boxed(target_value, priv->strings);
+    return TRUE;
+}
+
+static gboolean
+hostname_filter(NmtNewtEntry *entry, const char *text, int ch, int position, gpointer user_data)
+{
+    return g_ascii_isalnum(ch) || ch == '.' || ch == '-';
+}
+
+static NmtNewtWidget *
+nmt_address_list_create_widget(NmtWidgetList *list, int num)
+{
+    NmtAddressListPrivate *priv = NMT_ADDRESS_LIST_GET_PRIVATE(list);
+    NmtNewtWidget *        entry;
+
+    if (priv->list_type == NMT_ADDRESS_LIST_IP4_WITH_PREFIX)
+        entry = nmt_ip_entry_new(25, AF_INET, TRUE, FALSE);
+    else if (priv->list_type == NMT_ADDRESS_LIST_IP4)
+        entry = nmt_ip_entry_new(25, AF_INET, FALSE, FALSE);
+    else if (priv->list_type == NMT_ADDRESS_LIST_IP6_WITH_PREFIX)
+        entry = nmt_ip_entry_new(25, AF_INET6, TRUE, FALSE);
+    else if (priv->list_type == NMT_ADDRESS_LIST_IP6)
+        entry = nmt_ip_entry_new(25, AF_INET6, FALSE, FALSE);
+    else if (priv->list_type == NMT_ADDRESS_LIST_HOSTNAME) {
+        entry = nmt_newt_entry_new(25, NMT_NEWT_ENTRY_NONEMPTY);
+        nmt_newt_entry_set_filter(NMT_NEWT_ENTRY(entry), hostname_filter, list);
+    } else
+        g_assert_not_reached();
+
+    g_object_bind_property_full(list,
+                                "strings",
+                                entry,
+                                "text",
+                                G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE,
+                                strings_transform_to_entry,
+                                strings_transform_from_entry,
+                                GINT_TO_POINTER(num),
+                                NULL);
+
+    return entry;
+}
+
+static void
+nmt_address_list_add_clicked(NmtWidgetList *list)
+{
+    NmtAddressListPrivate *priv = NMT_ADDRESS_LIST_GET_PRIVATE(list);
+    int                    len;
+
+    len                    = priv->strings ? g_strv_length(priv->strings) : 0;
+    priv->strings          = g_renew(char *, priv->strings, len + 2);
+    priv->strings[len]     = g_strdup("");
+    priv->strings[len + 1] = NULL;
+
+    nmt_widget_list_set_length(list, len + 1);
+    g_object_notify(G_OBJECT(list), "strings");
+}
+
+static void
+nmt_address_list_remove_clicked(NmtWidgetList *list, int num)
+{
+    NmtAddressListPrivate *priv = NMT_ADDRESS_LIST_GET_PRIVATE(list);
+    int                    len;
+
+    len = g_strv_length(priv->strings);
+    g_free(priv->strings[num]);
+    memmove(priv->strings + num, priv->strings + num + 1, (len - num) * sizeof(char *));
+
+    nmt_widget_list_set_length(list, len - 1);
+    g_object_notify(G_OBJECT(list), "strings");
+}
+
+static void
+nmt_address_list_set_property(GObject *     object,
+                              guint         prop_id,
+                              const GValue *value,
+                              GParamSpec *  pspec)
+{
+    NmtAddressListPrivate *priv = NMT_ADDRESS_LIST_GET_PRIVATE(object);
+
+    switch (prop_id) {
+    case PROP_LIST_TYPE:
+        priv->list_type = g_value_get_uint(value);
+        break;
+    case PROP_STRINGS:
+        g_strfreev(priv->strings);
+        priv->strings = g_value_dup_boxed(value);
+        if (!priv->strings)
+            priv->strings = g_new0(char *, 1);
+        nmt_widget_list_set_length(NMT_WIDGET_LIST(object), g_strv_length(priv->strings));
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nmt_address_list_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+    NmtAddressListPrivate *priv = NMT_ADDRESS_LIST_GET_PRIVATE(object);
+
+    switch (prop_id) {
+    case PROP_LIST_TYPE:
+        g_value_set_uint(value, priv->list_type);
+        break;
+    case PROP_STRINGS:
+        g_value_set_boxed(value, priv->strings);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nmt_address_list_class_init(NmtAddressListClass *list_class)
+{
+    GObjectClass *      object_class      = G_OBJECT_CLASS(list_class);
+    NmtWidgetListClass *widget_list_class = NMT_WIDGET_LIST_CLASS(list_class);
+
+    g_type_class_add_private(list_class, sizeof(NmtAddressListPrivate));
+
+    /* virtual methods */
+    object_class->set_property = nmt_address_list_set_property;
+    object_class->get_property = nmt_address_list_get_property;
+
+    widget_list_class->create_widget  = nmt_address_list_create_widget;
+    widget_list_class->add_clicked    = nmt_address_list_add_clicked;
+    widget_list_class->remove_clicked = nmt_address_list_remove_clicked;
+
+    /**
+     * NmtAddressList:list-type:
+     *
+     * The type of address the list holds.
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_LIST_TYPE,
+        g_param_spec_uint("list-type",
+                          "",
+                          "",
+                          0,
+                          G_MAXUINT,
+                          0,
+                          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+    /**
+     * NmtAddressList:strings:
+     *
+     * The strings in the list's entries.
+     */
+    g_object_class_install_property(object_class,
+                                    PROP_STRINGS,
+                                    g_param_spec_boxed("strings",
+                                                       "",
+                                                       "",
+                                                       G_TYPE_STRV,
+                                                       G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+}
diff --git a/src/nmtui/nmt-address-list.h b/src/nmtui/nmt-address-list.h
new file mode 100644
index 00000000..fa356601
--- /dev/null
+++ b/src/nmtui/nmt-address-list.h
@@ -0,0 +1,43 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+#ifndef NMT_ADDRESS_LIST_H
+#define NMT_ADDRESS_LIST_H
+
+#include "nmt-widget-list.h"
+
+#define NMT_TYPE_ADDRESS_LIST (nmt_address_list_get_type())
+#define NMT_ADDRESS_LIST(obj) \
+    (G_TYPE_CHECK_INSTANCE_CAST((obj), NMT_TYPE_ADDRESS_LIST, NmtAddressList))
+#define NMT_ADDRESS_LIST_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NMT_TYPE_ADDRESS_LIST, NmtAddressListClass))
+#define NMT_IS_ADDRESS_LIST(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMT_TYPE_ADDRESS_LIST))
+#define NMT_IS_ADDRESS_LIST_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NMT_TYPE_ADDRESS_LIST))
+#define NMT_ADDRESS_LIST_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NMT_TYPE_ADDRESS_LIST, NmtAddressListClass))
+
+typedef struct {
+    NmtWidgetList parent;
+
+} NmtAddressList;
+
+typedef struct {
+    NmtWidgetListClass parent;
+
+} NmtAddressListClass;
+
+GType nmt_address_list_get_type(void);
+
+typedef enum {
+    NMT_ADDRESS_LIST_IP4_WITH_PREFIX,
+    NMT_ADDRESS_LIST_IP4,
+    NMT_ADDRESS_LIST_IP6_WITH_PREFIX,
+    NMT_ADDRESS_LIST_IP6,
+    NMT_ADDRESS_LIST_HOSTNAME
+} NmtAddressListType;
+
+NmtNewtWidget *nmt_address_list_new(NmtAddressListType list_type);
+
+#endif /* NMT_ADDRESS_LIST_H */
diff --git a/src/nmtui/nmt-connect-connection-list.c b/src/nmtui/nmt-connect-connection-list.c
new file mode 100644
index 00000000..4503dc92
--- /dev/null
+++ b/src/nmtui/nmt-connect-connection-list.c
@@ -0,0 +1,692 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 - 2017 Red Hat, Inc.
+ */
+
+/**
+ * SECTION:nmt-connect-connection-list
+ * @short_description: Connection list for "nmtui connect"
+ *
+ * #NmtConnectConnectionList is the list of devices, connections, and
+ * access points displayed by "nmtui connect".
+ */
+
+#include "libnm-client-aux-extern/nm-default-client.h"
+
+#include <stdlib.h>
+
+#include "nmtui.h"
+#include "nmt-connect-connection-list.h"
+#include "libnmc-base/nm-client-utils.h"
+
+G_DEFINE_TYPE(NmtConnectConnectionList, nmt_connect_connection_list, NMT_TYPE_NEWT_LISTBOX)
+
+#define NMT_CONNECT_CONNECTION_LIST_GET_PRIVATE(o)                 \
+    (G_TYPE_INSTANCE_GET_PRIVATE((o),                              \
+                                 NMT_TYPE_CONNECT_CONNECTION_LIST, \
+                                 NmtConnectConnectionListPrivate))
+
+typedef struct {
+    char *    name;
+    NMDevice *device;
+
+    int sort_order;
+
+    GSList *conns;
+} NmtConnectDevice;
+
+typedef struct {
+    const char *name;
+    char *      ssid;
+
+    NMConnection *      conn;
+    NMAccessPoint *     ap;
+    NMDevice *          device;
+    NMActiveConnection *active;
+} NmtConnectConnection;
+
+typedef struct {
+    GSList *nmt_devices;
+} NmtConnectConnectionListPrivate;
+
+/**
+ * nmt_connect_connection_list_new:
+ *
+ * Creates a new #NmtConnectConnectionList
+ *
+ * Returns: a new #NmtConnectConnectionList
+ */
+NmtNewtWidget *
+nmt_connect_connection_list_new(void)
+{
+    return g_object_new(NMT_TYPE_CONNECT_CONNECTION_LIST,
+                        "flags",
+                        NMT_NEWT_LISTBOX_SCROLL | NMT_NEWT_LISTBOX_BORDER,
+                        "skip-null-keys",
+                        TRUE,
+                        NULL);
+}
+
+static void
+nmt_connect_connection_list_init(NmtConnectConnectionList *list)
+{}
+
+static void
+nmt_connect_connection_free(NmtConnectConnection *nmtconn)
+{
+    g_clear_object(&nmtconn->conn);
+    g_clear_object(&nmtconn->ap);
+    g_clear_object(&nmtconn->active);
+    g_free(nmtconn->ssid);
+    g_slice_free(NmtConnectConnection, nmtconn);
+}
+
+static void
+nmt_connect_device_free(NmtConnectDevice *nmtdev)
+{
+    nm_clear_g_free(&nmtdev->name);
+    g_clear_object(&nmtdev->device);
+
+    g_slist_free_full(nmtdev->conns, (GDestroyNotify) nmt_connect_connection_free);
+    g_slice_free(NmtConnectDevice, nmtdev);
+}
+
+static const char *device_sort_order[]   = {"NMDeviceEthernet",
+                                          "NMDeviceVeth",
+                                          "NMDeviceInfiniband",
+                                          "NMDeviceWifi",
+                                          NM_SETTING_VLAN_SETTING_NAME,
+                                          NM_SETTING_BOND_SETTING_NAME,
+                                          NM_SETTING_TEAM_SETTING_NAME,
+                                          NM_SETTING_BRIDGE_SETTING_NAME,
+                                          NM_SETTING_IP_TUNNEL_SETTING_NAME,
+                                          "NMDeviceModem",
+                                          "NMDeviceBt"};
+static const int   device_sort_order_len = G_N_ELEMENTS(device_sort_order);
+
+static int
+get_sort_order_for_device(NMDevice *device)
+{
+    const char *type;
+    int         i;
+
+    type = G_OBJECT_TYPE_NAME(device);
+    for (i = 0; i < device_sort_order_len; i++) {
+        if (!strcmp(type, device_sort_order[i]))
+            return i;
+    }
+
+    return -1;
+}
+
+static int
+get_sort_order_for_connection(NMConnection *conn)
+{
+    NMSettingConnection *s_con;
+    const char *         type;
+    int                  i;
+
+    s_con = nm_connection_get_setting_connection(conn);
+    type  = nm_setting_connection_get_connection_type(s_con);
+
+    for (i = 0; i < device_sort_order_len; i++) {
+        if (!strcmp(type, device_sort_order[i]))
+            return i;
+    }
+
+    return -1;
+}
+
+static int
+sort_connections(gconstpointer a, gconstpointer b)
+{
+    NmtConnectConnection *nmta = (NmtConnectConnection *) a;
+    NmtConnectConnection *nmtb = (NmtConnectConnection *) b;
+
+    /* If nmta and nmtb both have NMConnections, sort them by timestamp */
+    if (nmta->conn && nmtb->conn) {
+        NMSettingConnection *s_con_a, *s_con_b;
+        guint64              time_a, time_b;
+
+        s_con_a = nm_connection_get_setting_connection(nmta->conn);
+        s_con_b = nm_connection_get_setting_connection(nmtb->conn);
+
+        time_a = nm_setting_connection_get_timestamp(s_con_a);
+        time_b = nm_setting_connection_get_timestamp(s_con_b);
+
+        return (int) (time_b - time_a);
+    }
+
+    /* If one is an NMConnection and the other is an NMAccessPoint, the
+     * connection comes first.
+     */
+    if (nmta->conn)
+        return -1;
+    else if (nmtb->conn)
+        return 1;
+
+    g_return_val_if_fail(nmta->ap && nmtb->ap, 0);
+
+    /* If both are access points, then sort by strength */
+    return nm_access_point_get_strength(nmtb->ap) - nm_access_point_get_strength(nmta->ap);
+}
+
+static void
+add_connections_for_device(NmtConnectDevice *nmtdev, const GPtrArray *connections)
+{
+    int i;
+
+    for (i = 0; i < connections->len; i++) {
+        NMConnection *       conn = connections->pdata[i];
+        NMSettingConnection *s_con;
+
+        s_con = nm_connection_get_setting_connection(conn);
+        if (nm_setting_connection_get_master(s_con))
+            continue;
+
+        if (nm_device_connection_valid(nmtdev->device, conn)) {
+            NmtConnectConnection *nmtconn = g_slice_new0(NmtConnectConnection);
+
+            nmtconn->name   = nm_connection_get_id(conn);
+            nmtconn->device = nmtdev->device;
+            nmtconn->conn   = g_object_ref(conn);
+            nmtdev->conns   = g_slist_prepend(nmtdev->conns, nmtconn);
+        }
+    }
+}
+
+/* stolen from nm-applet */
+static char *
+hash_ap(NMAccessPoint *ap)
+{
+    unsigned char input[66];
+    GBytes *      ssid;
+    NM80211Mode   mode;
+    guint32       flags;
+    guint32       wpa_flags;
+    guint32       rsn_flags;
+
+    memset(&input[0], 0, sizeof(input));
+
+    ssid = nm_access_point_get_ssid(ap);
+    if (ssid)
+        memcpy(input, g_bytes_get_data(ssid, NULL), g_bytes_get_size(ssid));
+
+    mode = nm_access_point_get_mode(ap);
+    if (mode == NM_802_11_MODE_INFRA)
+        input[32] |= (1 << 0);
+    else if (mode == NM_802_11_MODE_ADHOC)
+        input[32] |= (1 << 1);
+    else
+        input[32] |= (1 << 2);
+
+    /* Separate out no encryption, WEP-only, and WPA-capable */
+    flags     = nm_access_point_get_flags(ap);
+    wpa_flags = nm_access_point_get_wpa_flags(ap);
+    rsn_flags = nm_access_point_get_rsn_flags(ap);
+    if (!(flags & NM_802_11_AP_FLAGS_PRIVACY) && (wpa_flags == NM_802_11_AP_SEC_NONE)
+        && (rsn_flags == NM_802_11_AP_SEC_NONE))
+        input[32] |= (1 << 3);
+    else if ((flags & NM_802_11_AP_FLAGS_PRIVACY) && (wpa_flags == NM_802_11_AP_SEC_NONE)
+             && (rsn_flags == NM_802_11_AP_SEC_NONE))
+        input[32] |= (1 << 4);
+    else if (!(flags & NM_802_11_AP_FLAGS_PRIVACY) && (wpa_flags != NM_802_11_AP_SEC_NONE)
+             && (rsn_flags != NM_802_11_AP_SEC_NONE))
+        input[32] |= (1 << 5);
+    else
+        input[32] |= (1 << 6);
+
+    /* duplicate it */
+    memcpy(&input[33], &input[0], 32);
+    return g_compute_checksum_for_data(G_CHECKSUM_MD5, input, sizeof(input));
+}
+
+static void
+add_connections_for_aps(NmtConnectDevice *nmtdev, const GPtrArray *connections)
+{
+    NmtConnectConnection *nmtconn;
+    NMConnection *        conn;
+    NMAccessPoint *       ap;
+    const GPtrArray *     aps;
+    GHashTable *          seen_ssids;
+    GBytes *              ssid;
+    char *                ap_hash;
+    int                   i, c;
+
+    aps = nm_device_wifi_get_access_points(NM_DEVICE_WIFI(nmtdev->device));
+    if (!aps->len)
+        return;
+
+    seen_ssids = g_hash_table_new_full(nm_str_hash, g_str_equal, g_free, NULL);
+
+    for (i = 0; i < aps->len; i++) {
+        ap = aps->pdata[i];
+
+        if (!nm_access_point_get_ssid(ap))
+            continue;
+
+        ap_hash = hash_ap(ap);
+        if (g_hash_table_contains(seen_ssids, ap_hash)) {
+            g_free(ap_hash);
+            continue;
+        }
+        g_hash_table_add(seen_ssids, ap_hash);
+
+        nmtconn         = g_slice_new0(NmtConnectConnection);
+        nmtconn->device = nmtdev->device;
+        nmtconn->ap     = g_object_ref(ap);
+        ssid            = nm_access_point_get_ssid(ap);
+        if (ssid)
+            nmtconn->ssid =
+                nm_utils_ssid_to_utf8(g_bytes_get_data(ssid, NULL), g_bytes_get_size(ssid));
+
+        for (c = 0; c < connections->len; c++) {
+            conn = connections->pdata[c];
+            if (nm_device_connection_valid(nmtdev->device, conn)
+                && nm_access_point_connection_valid(ap, conn)) {
+                nmtconn->name = nm_connection_get_id(conn);
+                nmtconn->conn = g_object_ref(conn);
+                break;
+            }
+        }
+
+        if (!nmtconn->name)
+            nmtconn->name = nmtconn->ssid ?: "<unknown>";
+
+        nmtdev->conns = g_slist_prepend(nmtdev->conns, nmtconn);
+    }
+
+    g_hash_table_unref(seen_ssids);
+}
+
+static GSList *
+append_nmt_devices_for_devices(GSList *         nmt_devices,
+                               const GPtrArray *devices,
+                               char **          names,
+                               const GPtrArray *connections)
+{
+    NmtConnectDevice *nmtdev;
+    NMDevice *        device;
+    int               i, sort_order;
+
+    for (i = 0; i < devices->len; i++) {
+        device = devices->pdata[i];
+
+        sort_order = get_sort_order_for_device(device);
+        if (sort_order == -1)
+            continue;
+
+        nmtdev             = g_slice_new0(NmtConnectDevice);
+        nmtdev->name       = g_strdup(names[i]);
+        nmtdev->device     = g_object_ref(device);
+        nmtdev->sort_order = sort_order;
+
+        if (NM_IS_DEVICE_WIFI(device))
+            add_connections_for_aps(nmtdev, connections);
+        else
+            add_connections_for_device(nmtdev, connections);
+        nmtdev->conns = g_slist_sort(nmtdev->conns, sort_connections);
+
+        nmt_devices = g_slist_prepend(nmt_devices, nmtdev);
+    }
+
+    return nmt_devices;
+}
+
+static GSList *
+append_nmt_devices_for_virtual_devices(GSList *nmt_devices, const GPtrArray *connections)
+{
+    NmtConnectDevice *    nmtdev = NULL;
+    int                   i;
+    GHashTable *          devices_by_name;
+    char *                name;
+    NMConnection *        conn;
+    NmtConnectConnection *nmtconn;
+    int                   sort_order;
+
+    devices_by_name = g_hash_table_new(nm_str_hash, g_str_equal);
+
+    for (i = 0; i < connections->len; i++) {
+        conn       = connections->pdata[i];
+        sort_order = get_sort_order_for_connection(conn);
+        if (sort_order == -1)
+            continue;
+
+        name = nm_connection_get_virtual_device_description(conn);
+        if (name)
+            nmtdev = g_hash_table_lookup(devices_by_name, name);
+        if (nmtdev)
+            g_free(name);
+        else {
+            nmtdev             = g_slice_new0(NmtConnectDevice);
+            nmtdev->name       = name ?: g_strdup("Unknown");
+            nmtdev->sort_order = sort_order;
+
+            g_hash_table_insert(devices_by_name, nmtdev->name, nmtdev);
+            nmt_devices = g_slist_prepend(nmt_devices, nmtdev);
+        }
+
+        nmtconn       = g_slice_new0(NmtConnectConnection);
+        nmtconn->name = nm_connection_get_id(conn);
+        nmtconn->conn = g_object_ref(conn);
+
+        nmtdev->conns = g_slist_insert_sorted(nmtdev->conns, nmtconn, sort_connections);
+    }
+
+    g_hash_table_destroy(devices_by_name);
+    return nmt_devices;
+}
+
+static GSList *
+append_nmt_devices_for_vpns(GSList *nmt_devices, const GPtrArray *connections)
+{
+    NmtConnectDevice *    nmtdev;
+    int                   i;
+    NMConnection *        conn;
+    NmtConnectConnection *nmtconn;
+
+    nmtdev             = g_slice_new0(NmtConnectDevice);
+    nmtdev->name       = g_strdup(_("VPN"));
+    nmtdev->sort_order = 100;
+
+    for (i = 0; i < connections->len; i++) {
+        conn = connections->pdata[i];
+        if (!nm_connection_is_type(conn, NM_SETTING_VPN_SETTING_NAME))
+            continue;
+
+        nmtconn       = g_slice_new0(NmtConnectConnection);
+        nmtconn->name = nm_connection_get_id(conn);
+        nmtconn->conn = g_object_ref(conn);
+
+        nmtdev->conns = g_slist_insert_sorted(nmtdev->conns, nmtconn, sort_connections);
+    }
+
+    if (nmtdev->conns)
+        nmt_devices = g_slist_prepend(nmt_devices, nmtdev);
+    else
+        nmt_connect_device_free(nmtdev);
+
+    return nmt_devices;
+}
+
+static int
+sort_nmt_devices(gconstpointer a, gconstpointer b)
+{
+    NmtConnectDevice *nmta = (NmtConnectDevice *) a;
+    NmtConnectDevice *nmtb = (NmtConnectDevice *) b;
+
+    if (nmta->sort_order != nmtb->sort_order)
+        return nmta->sort_order - nmtb->sort_order;
+
+    return strcmp(nmta->name, nmtb->name);
+}
+
+static NMActiveConnection *
+connection_find_ac(NMConnection *conn, const GPtrArray *acs)
+{
+    NMActiveConnection *ac;
+    NMRemoteConnection *ac_conn;
+    int                 i;
+
+    for (i = 0; i < acs->len; i++) {
+        ac      = acs->pdata[i];
+        ac_conn = nm_active_connection_get_connection(ac);
+
+        if (conn == NM_CONNECTION(ac_conn))
+            return ac;
+    }
+
+    return NULL;
+}
+
+static void
+nmt_connect_connection_list_rebuild(NmtConnectConnectionList *list)
+{
+    NmtConnectConnectionListPrivate *priv    = NMT_CONNECT_CONNECTION_LIST_GET_PRIVATE(list);
+    NmtNewtListbox *                 listbox = NMT_NEWT_LISTBOX(list);
+    const GPtrArray *                devices, *acs, *connections;
+    int                              max_width;
+    char **                          names, *row, active_col;
+    const char *                     strength_col;
+    GSList *                         nmt_devices, *diter, *citer;
+    NmtConnectDevice *               nmtdev;
+    NmtConnectConnection *           nmtconn;
+
+    g_slist_free_full(priv->nmt_devices, (GDestroyNotify) nmt_connect_device_free);
+    priv->nmt_devices = NULL;
+    nmt_newt_listbox_clear(listbox);
+
+    devices     = nm_client_get_devices(nm_client);
+    acs         = nm_client_get_active_connections(nm_client);
+    connections = nm_client_get_connections(nm_client);
+
+    nmt_devices = NULL;
+
+    names       = nm_device_disambiguate_names((NMDevice **) devices->pdata, devices->len);
+    nmt_devices = append_nmt_devices_for_devices(nmt_devices, devices, names, connections);
+    g_strfreev(names);
+
+    nmt_devices = append_nmt_devices_for_virtual_devices(nmt_devices, connections);
+    nmt_devices = append_nmt_devices_for_vpns(nmt_devices, connections);
+
+    nmt_devices = g_slist_sort(nmt_devices, sort_nmt_devices);
+
+    max_width = 0;
+    for (diter = nmt_devices; diter; diter = diter->next) {
+        nmtdev = diter->data;
+        for (citer = nmtdev->conns; citer; citer = citer->next) {
+            nmtconn = citer->data;
+
+            max_width = MAX(max_width, nmt_newt_text_width(nmtconn->name));
+        }
+    }
+
+    for (diter = nmt_devices; diter; diter = diter->next) {
+        nmtdev = diter->data;
+
+        if (nmtdev->conns) {
+            if (diter != nmt_devices)
+                nmt_newt_listbox_append(listbox, "", NULL);
+            nmt_newt_listbox_append(listbox, nmtdev->name, NULL);
+        }
+
+        for (citer = nmtdev->conns; citer; citer = citer->next) {
+            nmtconn = citer->data;
+
+            if (nmtconn->conn)
+                nmtconn->active = connection_find_ac(nmtconn->conn, acs);
+            if (nmtconn->active) {
+                g_object_ref(nmtconn->active);
+                active_col = '*';
+            } else
+                active_col = ' ';
+
+            if (nmtconn->ap) {
+                guint8 strength = nm_access_point_get_strength(nmtconn->ap);
+
+                strength_col = nmc_wifi_strength_bars(strength);
+            } else
+                strength_col = NULL;
+
+            row = g_strdup_printf("%c %s%-*s%s%s",
+                                  active_col,
+                                  nmtconn->name,
+                                  (int) (max_width - nmt_newt_text_width(nmtconn->name)),
+                                  "",
+                                  strength_col ? " " : "",
+                                  strength_col ?: "");
+
+            nmt_newt_listbox_append(listbox, row, nmtconn);
+            g_free(row);
+        }
+    }
+
+    priv->nmt_devices = nmt_devices;
+
+    g_object_notify(G_OBJECT(listbox), "active");
+    g_object_notify(G_OBJECT(listbox), "active-key");
+}
+
+static void
+rebuild_on_property_changed(GObject *object, GParamSpec *spec, gpointer list)
+{
+    nmt_connect_connection_list_rebuild(list);
+}
+
+static void
+nmt_connect_connection_list_constructed(GObject *object)
+{
+    NmtConnectConnectionList *list = NMT_CONNECT_CONNECTION_LIST(object);
+
+    g_signal_connect(nm_client,
+                     "notify::" NM_CLIENT_ACTIVE_CONNECTIONS,
+                     G_CALLBACK(rebuild_on_property_changed),
+                     list);
+    g_signal_connect(nm_client,
+                     "notify::" NM_CLIENT_CONNECTIONS,
+                     G_CALLBACK(rebuild_on_property_changed),
+                     list);
+    g_signal_connect(nm_client,
+                     "notify::" NM_CLIENT_DEVICES,
+                     G_CALLBACK(rebuild_on_property_changed),
+                     list);
+
+    nmt_connect_connection_list_rebuild(list);
+
+    G_OBJECT_CLASS(nmt_connect_connection_list_parent_class)->constructed(object);
+}
+
+static void
+nmt_connect_connection_list_finalize(GObject *object)
+{
+    NmtConnectConnectionListPrivate *priv = NMT_CONNECT_CONNECTION_LIST_GET_PRIVATE(object);
+
+    g_slist_free_full(priv->nmt_devices, (GDestroyNotify) nmt_connect_device_free);
+
+    g_signal_handlers_disconnect_by_func(nm_client,
+                                         G_CALLBACK(rebuild_on_property_changed),
+                                         object);
+
+    G_OBJECT_CLASS(nmt_connect_connection_list_parent_class)->finalize(object);
+}
+
+static void
+nmt_connect_connection_list_class_init(NmtConnectConnectionListClass *list_class)
+{
+    GObjectClass *object_class = G_OBJECT_CLASS(list_class);
+
+    g_type_class_add_private(list_class, sizeof(NmtConnectConnectionListPrivate));
+
+    /* virtual methods */
+    object_class->constructed = nmt_connect_connection_list_constructed;
+    object_class->finalize    = nmt_connect_connection_list_finalize;
+}
+
+/**
+ * nmt_connect_connection_list_get_connection:
+ * @list: an #NmtConnectConnectionList
+ * @identifier: a connection ID or UUID, or device name
+ * @connection: (out) (transfer none): the #NMConnection to be activated
+ * @device: (out) (transfer none): the #NMDevice to activate @connection on
+ * @specific_object: (out) (transfer none): the "specific object" to connect to
+ * @active: (out) (transfer none): the #NMActiveConnection corresponding
+ *   to the selection, if any.
+ *
+ * Gets information about the indicated connection.
+ *
+ * Returns: %TRUE if there was a match, %FALSE if not.
+ */
+gboolean
+nmt_connect_connection_list_get_connection(NmtConnectConnectionList *list,
+                                           const char *              identifier,
+                                           NMConnection **           connection,
+                                           NMDevice **               device,
+                                           NMObject **               specific_object,
+                                           NMActiveConnection **     active)
+{
+    NmtConnectConnectionListPrivate *priv = NMT_CONNECT_CONNECTION_LIST_GET_PRIVATE(list);
+    GSList *                         diter, *citer;
+    NmtConnectDevice *               nmtdev;
+    NmtConnectConnection *           nmtconn = NULL;
+    NMConnection *                   conn    = NULL;
+
+    g_return_val_if_fail(identifier, FALSE);
+
+    if (nm_utils_is_uuid(identifier))
+        conn = NM_CONNECTION(nm_client_get_connection_by_uuid(nm_client, identifier));
+    if (!conn)
+        conn = NM_CONNECTION(nm_client_get_connection_by_id(nm_client, identifier));
+
+    for (diter = priv->nmt_devices; diter; diter = diter->next) {
+        nmtdev = diter->data;
+        if (!nmtdev->conns)
+            continue;
+
+        for (citer = nmtdev->conns; citer; citer = citer->next) {
+            nmtconn = citer->data;
+            if (conn) {
+                if (conn == nmtconn->conn)
+                    goto found;
+            } else if (nm_streq0(identifier, nmtconn->ssid))
+                goto found;
+        }
+
+        if (!conn && nmtdev->device
+            && nm_streq0(identifier, nm_device_get_ip_iface(nmtdev->device))) {
+            nmtconn = nmtdev->conns->data;
+            goto found;
+        }
+    }
+
+    return FALSE;
+
+found:
+    if (connection)
+        *connection = nmtconn->conn;
+    if (device)
+        *device = nmtconn->device;
+    if (specific_object)
+        *specific_object = NM_OBJECT(nmtconn->ap);
+    if (active)
+        *active = nmtconn->active;
+
+    return TRUE;
+}
+
+/**
+ * nmt_connect_connection_list_get_selection:
+ * @list: an #NmtConnectConnectionList
+ * @connection: (out) (transfer none): the #NMConnection to be activated
+ * @device: (out) (transfer none): the #NMDevice to activate @connection on
+ * @specific_object: (out) (transfer none): the "specific object" to connect to
+ * @active: (out) (transfer none): the #NMActiveConnection corresponding
+ *   to the selection, if any.
+ *
+ * Gets information about the selected row.
+ *
+ * Returns: %TRUE if there is a selection, %FALSE if not.
+ */
+gboolean
+nmt_connect_connection_list_get_selection(NmtConnectConnectionList *list,
+                                          NMConnection **           connection,
+                                          NMDevice **               device,
+                                          NMObject **               specific_object,
+                                          NMActiveConnection **     active)
+{
+    NmtConnectConnection *nmtconn;
+
+    nmtconn = nmt_newt_listbox_get_active_key(NMT_NEWT_LISTBOX(list));
+    if (!nmtconn)
+        return FALSE;
+
+    if (connection)
+        *connection = nmtconn->conn;
+    if (device)
+        *device = nmtconn->device;
+    if (specific_object)
+        *specific_object = NM_OBJECT(nmtconn->ap);
+    if (active)
+        *active = nmtconn->active;
+
+    return TRUE;
+}
diff --git a/src/nmtui/nmt-connect-connection-list.h b/src/nmtui/nmt-connect-connection-list.h
new file mode 100644
index 00000000..15a319b1
--- /dev/null
+++ b/src/nmtui/nmt-connect-connection-list.h
@@ -0,0 +1,53 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+#ifndef NMT_CONNECT_CONNECTION_LIST_H
+#define NMT_CONNECT_CONNECTION_LIST_H
+
+#include "libnmt-newt/nmt-newt.h"
+
+#define NMT_TYPE_CONNECT_CONNECTION_LIST (nmt_connect_connection_list_get_type())
+#define NMT_CONNECT_CONNECTION_LIST(obj) \
+    (G_TYPE_CHECK_INSTANCE_CAST((obj), NMT_TYPE_CONNECT_CONNECTION_LIST, NmtConnectConnectionList))
+#define NMT_CONNECT_CONNECTION_LIST_CLASS(klass)               \
+    (G_TYPE_CHECK_CLASS_CAST((klass),                          \
+                             NMT_TYPE_CONNECT_CONNECTION_LIST, \
+                             NmtConnectConnectionListClass))
+#define NMT_IS_CONNECT_CONNECTION_LIST(obj) \
+    (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMT_TYPE_CONNECT_CONNECTION_LIST))
+#define NMT_IS_CONNECT_CONNECTION_LIST_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_TYPE((klass), NMT_TYPE_CONNECT_CONNECTION_LIST))
+#define NMT_CONNECT_CONNECTION_LIST_GET_CLASS(obj)               \
+    (G_TYPE_INSTANCE_GET_CLASS((obj),                            \
+                               NMT_TYPE_CONNECT_CONNECTION_LIST, \
+                               NmtConnectConnectionListClass))
+
+typedef struct {
+    NmtNewtListbox parent;
+
+} NmtConnectConnectionList;
+
+typedef struct {
+    NmtNewtListboxClass parent;
+
+} NmtConnectConnectionListClass;
+
+GType nmt_connect_connection_list_get_type(void);
+
+NmtNewtWidget *nmt_connect_connection_list_new(void);
+
+gboolean nmt_connect_connection_list_get_connection(NmtConnectConnectionList *list,
+                                                    const char *              identifier,
+                                                    NMConnection **           connection,
+                                                    NMDevice **               device,
+                                                    NMObject **               specific_object,
+                                                    NMActiveConnection **     active);
+gboolean nmt_connect_connection_list_get_selection(NmtConnectConnectionList *list,
+                                                   NMConnection **           connection,
+                                                   NMDevice **               device,
+                                                   NMObject **               specific_object,
+                                                   NMActiveConnection **     active);
+
+#endif /* NMT_CONNECT_CONNECTION_LIST_H */
diff --git a/src/nmtui/nmt-device-entry.c b/src/nmtui/nmt-device-entry.c
new file mode 100644
index 00000000..e831f4e4
--- /dev/null
+++ b/src/nmtui/nmt-device-entry.c
@@ -0,0 +1,555 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+/**
+ * SECTION:nmt-device-entry
+ * @short_description: #NmtNewtEntry for identifying a device
+ *
+ * #NmtDeviceEntry provides a widget for identifying a device, either
+ * by interface name or by hardware address. The user can enter either
+ * value, and the entry's #NmtDeviceEntry:interface-name or
+ * #NmtDeviceEntry:mac-address property will be set accordingly. If
+ * the entry recognizes the interface name or mac address typed in as
+ * matching a known #NMDevice, then it will also display the other
+ * property in parentheses.
+ *
+ * FIXME: #NmtDeviceEntry is currently an #NmtEditorGrid object, so that
+ * we can possibly eventually add a button to its "extra" field, that
+ * would pop up a form for selecting a device. But if we're not going
+ * to implement that then we should make it just an #NmtNewtEntry.
+ */
+
+#include "libnm-client-aux-extern/nm-default-client.h"
+
+#include "nmt-device-entry.h"
+
+#include <sys/socket.h>
+#include <linux/if_arp.h>
+
+#include "nmtui.h"
+
+G_DEFINE_TYPE(NmtDeviceEntry, nmt_device_entry, NMT_TYPE_EDITOR_GRID)
+
+#define NMT_DEVICE_ENTRY_GET_PRIVATE(o) \
+    (G_TYPE_INSTANCE_GET_PRIVATE((o), NMT_TYPE_DEVICE_ENTRY, NmtDeviceEntryPrivate))
+
+typedef struct {
+    GType                      hardware_type;
+    NmtDeviceEntryDeviceFilter device_filter;
+    gpointer                   device_filter_data;
+    int                        arptype;
+
+    char *interface_name;
+    char *mac_address;
+
+    char *         label;
+    NmtNewtEntry * entry;
+    NmtNewtWidget *button;
+
+    gboolean updating;
+} NmtDeviceEntryPrivate;
+
+enum {
+    PROP_0,
+    PROP_LABEL,
+    PROP_WIDTH,
+    PROP_HARDWARE_TYPE,
+    PROP_INTERFACE_NAME,
+    PROP_MAC_ADDRESS,
+
+    LAST_PROP
+};
+
+/**
+ * nmt_device_entry_new:
+ * @label: the label for the entry
+ * @width: the width of the entry
+ * @hardware_type: the type of #NMDevice to be selected, or
+ *   %G_TYPE_NONE if this is for a virtual device type.
+ *
+ * Creates a new #NmtDeviceEntry, for identifying a device of type
+ * @hardware_type. If @hardware_type is %G_TYPE_NONE (and you do not
+ * set a #NmtDeviceEntryDeviceFilter), then this will only allow
+ * specifying an interface name, not a hardware address.
+ *
+ * Returns: a new #NmtDeviceEntry.
+ */
+NmtNewtWidget *
+nmt_device_entry_new(const char *label, int width, GType hardware_type)
+{
+    return g_object_new(NMT_TYPE_DEVICE_ENTRY,
+                        "label",
+                        label,
+                        "width",
+                        width,
+                        "hardware-type",
+                        hardware_type,
+                        NULL);
+}
+
+static gboolean
+device_entry_parse(NmtDeviceEntry *deventry,
+                   const char *    text,
+                   char **         interface_name,
+                   char **         mac_address)
+{
+    NmtDeviceEntryPrivate *priv = NMT_DEVICE_ENTRY_GET_PRIVATE(deventry);
+    guint8                 buf[NM_UTILS_HWADDR_LEN_MAX];
+    char **                words;
+    int                    len;
+
+    *interface_name = *mac_address = NULL;
+    if (!*text)
+        return TRUE;
+
+    if (priv->hardware_type == G_TYPE_NONE && !priv->device_filter) {
+        if (nm_utils_ifname_valid_kernel(text, NULL)) {
+            *interface_name = g_strdup(text);
+            return TRUE;
+        } else
+            return FALSE;
+    }
+
+    words = g_strsplit(text, " ", -1);
+    if (g_strv_length(words) > 2) {
+        g_strfreev(words);
+        return FALSE;
+    }
+
+    if (words[1]) {
+        len = strlen(words[1]);
+        if (len < 3 || words[1][0] != '(' || words[1][len - 1] != ')')
+            goto fail;
+
+        memmove(words[1], words[1] + 1, len - 2);
+        words[1][len - 2] = '\0';
+    }
+
+    len = nm_utils_hwaddr_len(priv->arptype);
+    if (nm_utils_hwaddr_aton(words[0], buf, len)
+        && (!words[1] || nm_utils_ifname_valid_kernel(words[1], NULL))) {
+        *mac_address    = words[0];
+        *interface_name = NULL;
+        g_free(words);
+        return TRUE;
+    } else if (nm_utils_ifname_valid_kernel(words[0], NULL)
+               && (!words[1] || nm_utils_hwaddr_aton(words[1], buf, len))) {
+        *interface_name = words[0];
+        *mac_address    = NULL;
+        g_free(words);
+        return TRUE;
+    }
+
+fail:
+    g_strfreev(words);
+    return FALSE;
+}
+
+static gboolean
+device_entry_validate(NmtNewtEntry *entry, const char *text, gpointer user_data)
+{
+    NmtDeviceEntry *deventry = user_data;
+    char *          ifname, *mac;
+
+    if (!device_entry_parse(deventry, text, &ifname, &mac))
+        return FALSE;
+
+    g_free(ifname);
+    g_free(mac);
+    return TRUE;
+}
+
+static NMDevice *
+find_device_by_interface_name(NmtDeviceEntry *deventry, const char *interface_name)
+{
+    NmtDeviceEntryPrivate *priv = NMT_DEVICE_ENTRY_GET_PRIVATE(deventry);
+    const GPtrArray *      devices;
+    NMDevice *             device = NULL;
+    int                    i;
+
+    devices = nm_client_get_devices(nm_client);
+    for (i = 0; i < devices->len && !device; i++) {
+        NMDevice *candidate = devices->pdata[i];
+
+        if (priv->hardware_type != G_TYPE_NONE
+            && !G_TYPE_CHECK_INSTANCE_TYPE(candidate, priv->hardware_type))
+            continue;
+
+        if (priv->device_filter
+            && !priv->device_filter(deventry, candidate, priv->device_filter_data))
+            continue;
+
+        if (!g_strcmp0(interface_name, nm_device_get_iface(candidate)))
+            device = candidate;
+    }
+
+    return device;
+}
+
+static NMDevice *
+find_device_by_mac_address(NmtDeviceEntry *deventry, const char *mac_address)
+{
+    NmtDeviceEntryPrivate *priv = NMT_DEVICE_ENTRY_GET_PRIVATE(deventry);
+    const GPtrArray *      devices;
+    NMDevice *             device = NULL;
+    int                    i;
+
+    devices = nm_client_get_devices(nm_client);
+    for (i = 0; i < devices->len && !device; i++) {
+        NMDevice *candidate = devices->pdata[i];
+        char *    hwaddr;
+
+        if (priv->hardware_type != G_TYPE_NONE
+            && !G_TYPE_CHECK_INSTANCE_TYPE(candidate, priv->hardware_type))
+            continue;
+
+        if (priv->device_filter
+            && !priv->device_filter(deventry, candidate, priv->device_filter_data))
+            continue;
+
+        g_object_get(G_OBJECT(candidate), "hw-address", &hwaddr, NULL);
+        if (hwaddr && !g_ascii_strcasecmp(mac_address, hwaddr))
+            device = candidate;
+        g_free(hwaddr);
+    }
+
+    return device;
+}
+
+static void
+update_entry(NmtDeviceEntry *deventry)
+{
+    NmtDeviceEntryPrivate *priv = NMT_DEVICE_ENTRY_GET_PRIVATE(deventry);
+    const char *           ifname;
+    char *                 mac, *text;
+    NMDevice *             ifname_device, *mac_device;
+
+    if (priv->interface_name) {
+        ifname        = priv->interface_name;
+        ifname_device = find_device_by_interface_name(deventry, priv->interface_name);
+    } else {
+        ifname        = NULL;
+        ifname_device = NULL;
+    }
+
+    if (priv->mac_address) {
+        mac        = g_strdup(priv->mac_address);
+        mac_device = find_device_by_mac_address(deventry, priv->mac_address);
+    } else {
+        mac        = NULL;
+        mac_device = NULL;
+    }
+
+    if (!ifname && mac_device)
+        ifname = nm_device_get_iface(mac_device);
+    if (!mac && ifname_device && (priv->hardware_type != G_TYPE_NONE))
+        g_object_get(G_OBJECT(ifname_device), "hw-address", &mac, NULL);
+
+    if (ifname_device && mac_device && ifname_device != mac_device) {
+        /* Mismatch! */
+        text = g_strdup_printf("%s != %s", priv->interface_name, mac);
+    } else if (ifname && mac) {
+        if (ifname_device)
+            text = g_strdup_printf("%s (%s)", ifname, mac);
+        else
+            text = g_strdup_printf("%s (%s)", mac, ifname);
+    } else if (ifname)
+        text = g_strdup(ifname);
+    else if (mac)
+        text = g_strdup(mac);
+    else
+        text = g_strdup("");
+
+    priv->updating = TRUE;
+    g_object_set(G_OBJECT(priv->entry), "text", text, NULL);
+    priv->updating = FALSE;
+    g_free(text);
+
+    g_free(mac);
+}
+
+static gboolean
+nmt_device_entry_set_interface_name(NmtDeviceEntry *deventry, const char *interface_name)
+{
+    NmtDeviceEntryPrivate *priv = NMT_DEVICE_ENTRY_GET_PRIVATE(deventry);
+
+    if (g_strcmp0(interface_name, priv->interface_name) != 0) {
+        g_free(priv->interface_name);
+        priv->interface_name = g_strdup(interface_name);
+
+        g_object_notify(G_OBJECT(deventry), "interface-name");
+        return TRUE;
+    } else
+        return FALSE;
+}
+
+static gboolean
+nmt_device_entry_set_mac_address(NmtDeviceEntry *deventry, const char *mac_address)
+{
+    NmtDeviceEntryPrivate *priv = NMT_DEVICE_ENTRY_GET_PRIVATE(deventry);
+    gboolean               changed;
+
+    if (mac_address && !priv->mac_address) {
+        priv->mac_address = g_strdup(mac_address);
+        changed           = TRUE;
+    } else if (!mac_address && priv->mac_address) {
+        nm_clear_g_free(&priv->mac_address);
+        changed = TRUE;
+    } else if (mac_address && priv->mac_address
+               && !nm_utils_hwaddr_matches(mac_address, -1, priv->mac_address, -1)) {
+        g_free(priv->mac_address);
+        priv->mac_address = g_strdup(mac_address);
+        changed           = TRUE;
+    } else
+        changed = FALSE;
+
+    if (changed)
+        g_object_notify(G_OBJECT(deventry), "mac-address");
+    return changed;
+}
+
+static void
+entry_text_changed(GObject *object, GParamSpec *pspec, gpointer deventry)
+{
+    NmtDeviceEntryPrivate *priv = NMT_DEVICE_ENTRY_GET_PRIVATE(deventry);
+    const char *           text;
+    char *                 ifname, *mac;
+
+    if (priv->updating)
+        return;
+
+    text = nmt_newt_entry_get_text(priv->entry);
+    if (!device_entry_parse(deventry, text, &ifname, &mac))
+        return;
+
+    nmt_device_entry_set_interface_name(deventry, ifname);
+    g_free(ifname);
+
+    nmt_device_entry_set_mac_address(deventry, mac);
+    g_free(mac);
+}
+
+static void
+nmt_device_entry_init(NmtDeviceEntry *deventry)
+{
+    NmtDeviceEntryPrivate *priv = NMT_DEVICE_ENTRY_GET_PRIVATE(deventry);
+    NmtNewtWidget *        entry;
+
+    priv->hardware_type = G_TYPE_NONE;
+
+    entry       = nmt_newt_entry_new(-1, 0);
+    priv->entry = NMT_NEWT_ENTRY(entry);
+    nmt_newt_entry_set_validator(priv->entry, device_entry_validate, deventry);
+    g_signal_connect(priv->entry, "notify::text", G_CALLBACK(entry_text_changed), deventry);
+
+#if 0
+    priv->button = nmt_newt_button_new (_("Select..."));
+    g_signal_connect (priv->button, "clicked",
+                      G_CALLBACK (do_select_dialog), deventry);
+#endif
+}
+
+static void
+nmt_device_entry_constructed(GObject *object)
+{
+    NmtDeviceEntryPrivate *priv = NMT_DEVICE_ENTRY_GET_PRIVATE(object);
+
+    nmt_editor_grid_append(NMT_EDITOR_GRID(object),
+                           priv->label,
+                           NMT_NEWT_WIDGET(priv->entry),
+                           NULL);
+
+    G_OBJECT_CLASS(nmt_device_entry_parent_class)->constructed(object);
+}
+
+static void
+nmt_device_entry_finalize(GObject *object)
+{
+    NmtDeviceEntryPrivate *priv = NMT_DEVICE_ENTRY_GET_PRIVATE(object);
+
+    g_free(priv->interface_name);
+    g_free(priv->mac_address);
+
+    G_OBJECT_CLASS(nmt_device_entry_parent_class)->finalize(object);
+}
+
+/**
+ * NmtDeviceEntryDeviceFilter:
+ * @deventry: the #NmtDeviceEntry
+ * @device: an #NMDevice
+ * @user_data: user data
+ *
+ * Filter function for determining which devices can be specified
+ * on an entry.
+ *
+ * Returns: %TRUE if @device is acceptable for @deventry
+ */
+
+/**
+ * nmt_device_entry_set_device_filter:
+ * @deventry: the #NmtDeviceEntry
+ * @filter: the filter
+ * @user_data: data for @filter
+ *
+ * Sets a device filter on @deventry. Only devices that pass @filter
+ * will be recognized by @deventry.
+ *
+ * If the entry's #NmtDeviceEntry:hardware-type is not %G_TYPE_NONE,
+ * then only devices that both match the hardware type and are
+ * accepted by the filter will be allowed.
+ */
+void
+nmt_device_entry_set_device_filter(NmtDeviceEntry *           deventry,
+                                   NmtDeviceEntryDeviceFilter filter,
+                                   gpointer                   user_data)
+{
+    NmtDeviceEntryPrivate *priv = NMT_DEVICE_ENTRY_GET_PRIVATE(deventry);
+
+    priv->device_filter      = filter;
+    priv->device_filter_data = user_data;
+}
+
+static void
+nmt_device_entry_set_property(GObject *     object,
+                              guint         prop_id,
+                              const GValue *value,
+                              GParamSpec *  pspec)
+{
+    NmtDeviceEntry *       deventry = NMT_DEVICE_ENTRY(object);
+    NmtDeviceEntryPrivate *priv     = NMT_DEVICE_ENTRY_GET_PRIVATE(deventry);
+    const char *           interface_name;
+    const char *           mac_address;
+
+    switch (prop_id) {
+    case PROP_LABEL:
+        priv->label = g_value_dup_string(value);
+        break;
+    case PROP_WIDTH:
+        nmt_newt_entry_set_width(priv->entry, g_value_get_int(value));
+        break;
+    case PROP_HARDWARE_TYPE:
+        priv->hardware_type = g_value_get_gtype(value);
+        priv->arptype =
+            (priv->hardware_type == NM_TYPE_DEVICE_INFINIBAND) ? ARPHRD_INFINIBAND : ARPHRD_ETHER;
+        break;
+    case PROP_INTERFACE_NAME:
+        interface_name = g_value_get_string(value);
+        if (nmt_device_entry_set_interface_name(deventry, interface_name))
+            update_entry(deventry);
+        break;
+    case PROP_MAC_ADDRESS:
+        mac_address = g_value_get_string(value);
+        if (nmt_device_entry_set_mac_address(deventry, mac_address))
+            update_entry(deventry);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nmt_device_entry_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+    NmtDeviceEntryPrivate *priv = NMT_DEVICE_ENTRY_GET_PRIVATE(object);
+
+    switch (prop_id) {
+    case PROP_LABEL:
+        g_value_set_string(value, priv->label);
+        break;
+    case PROP_WIDTH:
+        g_value_set_int(value, nmt_newt_entry_get_width(priv->entry));
+        break;
+    case PROP_HARDWARE_TYPE:
+        g_value_set_gtype(value, priv->hardware_type);
+        break;
+    case PROP_INTERFACE_NAME:
+        g_value_set_string(value, priv->interface_name);
+        break;
+    case PROP_MAC_ADDRESS:
+        g_value_set_string(value, priv->mac_address);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nmt_device_entry_class_init(NmtDeviceEntryClass *deventry_class)
+{
+    GObjectClass *object_class = G_OBJECT_CLASS(deventry_class);
+
+    g_type_class_add_private(deventry_class, sizeof(NmtDeviceEntryPrivate));
+
+    /* virtual methods */
+    object_class->constructed  = nmt_device_entry_constructed;
+    object_class->set_property = nmt_device_entry_set_property;
+    object_class->get_property = nmt_device_entry_get_property;
+    object_class->finalize     = nmt_device_entry_finalize;
+
+    /**
+     * NmtDeviceEntry:label:
+     *
+     * The entry's label
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_LABEL,
+        g_param_spec_string("label",
+                            "",
+                            "",
+                            NULL,
+                            G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+    /**
+     * NmtDeviceEntry:width:
+     *
+     * The entry's width in characters
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_WIDTH,
+        g_param_spec_int("width", "", "", -1, 80, -1, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+    /**
+     * NmtDeviceEntry:hardware-type:
+     *
+     * The type of #NMDevice to limit the entry to, or %G_TYPE_NONE
+     * if the entry is for a virtual device and should not accept
+     * hardware addresses.
+     */
+    g_object_class_install_property(object_class,
+                                    PROP_HARDWARE_TYPE,
+                                    g_param_spec_gtype("hardware-type",
+                                                       "",
+                                                       "",
+                                                       G_TYPE_NONE,
+                                                       G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+    /**
+     * NmtDeviceEntry:interface-name:
+     *
+     * The interface name of the device identified by the entry.
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_INTERFACE_NAME,
+        g_param_spec_string("interface-name",
+                            "",
+                            "",
+                            NULL,
+                            G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+    /**
+     * NmtDeviceEntry:mac-address:
+     *
+     * The hardware address of the device identified by the entry.
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_MAC_ADDRESS,
+        g_param_spec_string("mac-address",
+                            "",
+                            "",
+                            NULL,
+                            G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+}
diff --git a/src/nmtui/nmt-device-entry.h b/src/nmtui/nmt-device-entry.h
new file mode 100644
index 00000000..13f82bc8
--- /dev/null
+++ b/src/nmtui/nmt-device-entry.h
@@ -0,0 +1,42 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+#ifndef NMT_DEVICE_ENTRY_H
+#define NMT_DEVICE_ENTRY_H
+
+#include "nmt-editor-grid.h"
+
+#define NMT_TYPE_DEVICE_ENTRY (nmt_device_entry_get_type())
+#define NMT_DEVICE_ENTRY(obj) \
+    (G_TYPE_CHECK_INSTANCE_CAST((obj), NMT_TYPE_DEVICE_ENTRY, NmtDeviceEntry))
+#define NMT_DEVICE_ENTRY_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NMT_TYPE_DEVICE_ENTRY, NmtDeviceEntryClass))
+#define NMT_IS_DEVICE_ENTRY(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMT_TYPE_DEVICE_ENTRY))
+#define NMT_IS_DEVICE_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NMT_TYPE_DEVICE_ENTRY))
+#define NMT_DEVICE_ENTRY_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NMT_TYPE_DEVICE_ENTRY, NmtDeviceEntryClass))
+
+typedef struct {
+    NmtEditorGrid parent;
+
+} NmtDeviceEntry;
+
+typedef struct {
+    NmtEditorGridClass parent;
+
+} NmtDeviceEntryClass;
+
+GType nmt_device_entry_get_type(void);
+
+NmtNewtWidget *nmt_device_entry_new(const char *label, int width, GType hardware_type);
+
+typedef gboolean (*NmtDeviceEntryDeviceFilter)(NmtDeviceEntry *deventry,
+                                               NMDevice *      device,
+                                               gpointer        user_data);
+void nmt_device_entry_set_device_filter(NmtDeviceEntry *           deventry,
+                                        NmtDeviceEntryDeviceFilter filter,
+                                        gpointer                   user_data);
+
+#endif /* NMT_DEVICE_ENTRY_H */
diff --git a/src/nmtui/nmt-edit-connection-list.c b/src/nmtui/nmt-edit-connection-list.c
new file mode 100644
index 00000000..9b31e06f
--- /dev/null
+++ b/src/nmtui/nmt-edit-connection-list.c
@@ -0,0 +1,584 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+/**
+ * SECTION:nmt-edit-connection-list
+ * @short_description: Connection list for "nmtui edit"
+ *
+ * #NmtEditConnectionList is the list of connections displayed by
+ * "nmtui edit".
+ */
+
+#include "libnm-client-aux-extern/nm-default-client.h"
+
+#include "nmtui.h"
+#include "nmtui-edit.h"
+#include "nmt-edit-connection-list.h"
+#include "nmt-editor.h"
+
+#include "nm-editor-utils.h"
+
+G_DEFINE_TYPE(NmtEditConnectionList, nmt_edit_connection_list, NMT_TYPE_NEWT_GRID)
+
+#define NMT_EDIT_CONNECTION_LIST_GET_PRIVATE(o) \
+    (G_TYPE_INSTANCE_GET_PRIVATE((o), NMT_TYPE_EDIT_CONNECTION_LIST, NmtEditConnectionListPrivate))
+
+typedef struct {
+    GSList *connections;
+
+    gboolean                    grouped;
+    NmtEditConnectionListFilter connection_filter;
+    gpointer                    connection_filter_data;
+
+    NmtNewtListbox *  listbox;
+    NmtNewtButtonBox *buttons;
+
+    NmtNewtWidget *add;
+    NmtNewtWidget *edit;
+    NmtNewtWidget *delete;
+    NmtNewtWidget *extra;
+} NmtEditConnectionListPrivate;
+
+enum {
+    PROP_0,
+
+    PROP_GROUPED,
+    PROP_CONNECTION_FILTER,
+    PROP_CONNECTION_FILTER_DATA,
+    PROP_EXTRA_WIDGET,
+    PROP_CONNECTIONS,
+    PROP_NUM_CONNECTIONS,
+
+    LAST_PROP
+};
+
+enum {
+    ADD_CONNECTION,
+    EDIT_CONNECTION,
+    REMOVE_CONNECTION,
+
+    LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = {0};
+
+static void add_clicked(NmtNewtButton *button, gpointer list);
+static void edit_clicked(NmtNewtButton *button, gpointer list);
+static void delete_clicked(NmtNewtButton *button, gpointer list);
+static void listbox_activated(NmtNewtWidget *listbox, gpointer list);
+
+static void
+nmt_edit_connection_list_init(NmtEditConnectionList *list)
+{
+    NmtEditConnectionListPrivate *priv = NMT_EDIT_CONNECTION_LIST_GET_PRIVATE(list);
+    NmtNewtWidget *               listbox, *buttons;
+    NmtNewtGrid *                 grid = NMT_NEWT_GRID(list);
+
+    listbox       = g_object_new(NMT_TYPE_NEWT_LISTBOX,
+                           "flags",
+                           NMT_NEWT_LISTBOX_SCROLL | NMT_NEWT_LISTBOX_BORDER,
+                           "skip-null-keys",
+                           TRUE,
+                           NULL);
+    priv->listbox = NMT_NEWT_LISTBOX(listbox);
+    nmt_newt_grid_add(grid, listbox, 0, 0);
+    nmt_newt_grid_set_flags(grid,
+                            listbox,
+                            NMT_NEWT_GRID_FILL_X | NMT_NEWT_GRID_FILL_Y | NMT_NEWT_GRID_EXPAND_X
+                                | NMT_NEWT_GRID_EXPAND_Y);
+    g_signal_connect(priv->listbox, "activated", G_CALLBACK(listbox_activated), list);
+
+    buttons       = nmt_newt_button_box_new(NMT_NEWT_BUTTON_BOX_VERTICAL);
+    priv->buttons = NMT_NEWT_BUTTON_BOX(buttons);
+    nmt_newt_grid_add(grid, buttons, 1, 0);
+    nmt_newt_widget_set_padding(buttons, 1, 1, 0, 1);
+    nmt_newt_grid_set_flags(grid,
+                            buttons,
+                            NMT_NEWT_GRID_FILL_X | NMT_NEWT_GRID_FILL_Y | NMT_NEWT_GRID_EXPAND_Y);
+
+    priv->add = nmt_newt_button_box_add_start(priv->buttons, _("Add"));
+    g_signal_connect(priv->add, "clicked", G_CALLBACK(add_clicked), list);
+
+    priv->edit = nmt_newt_button_box_add_start(priv->buttons, _("Edit..."));
+    g_signal_connect(priv->edit, "clicked", G_CALLBACK(edit_clicked), list);
+
+    priv->delete = nmt_newt_button_box_add_start(priv->buttons, _("Delete"));
+    g_signal_connect(priv->delete, "clicked", G_CALLBACK(delete_clicked), list);
+}
+
+static int
+sort_by_timestamp(gconstpointer a, gconstpointer b)
+{
+    NMSettingConnection *s_con_a, *s_con_b;
+    guint64              time_a, time_b;
+
+    s_con_a = nm_connection_get_setting_connection((NMConnection *) a);
+    s_con_b = nm_connection_get_setting_connection((NMConnection *) b);
+
+    time_a = nm_setting_connection_get_timestamp(s_con_a);
+    time_b = nm_setting_connection_get_timestamp(s_con_b);
+
+    return (int) (time_b - time_a);
+}
+
+static void nmt_edit_connection_list_rebuild(NmtEditConnectionList *list);
+
+static void
+rebuild_on_connection_changed(NMRemoteConnection *connection, gpointer list)
+{
+    nmt_edit_connection_list_rebuild(list);
+}
+
+static void
+free_connections(NmtEditConnectionList *list)
+{
+    NmtEditConnectionListPrivate *priv = NMT_EDIT_CONNECTION_LIST_GET_PRIVATE(list);
+    NMConnection *                conn;
+    GSList *                      iter;
+
+    for (iter = priv->connections; iter; iter = iter->next) {
+        conn = iter->data;
+
+        g_signal_handlers_disconnect_by_func(conn, G_CALLBACK(rebuild_on_connection_changed), list);
+        g_object_unref(conn);
+    }
+    g_slist_free(priv->connections);
+    priv->connections = NULL;
+}
+
+static void
+nmt_edit_connection_list_rebuild(NmtEditConnectionList *list)
+{
+    NmtEditConnectionListPrivate *priv = NMT_EDIT_CONNECTION_LIST_GET_PRIVATE(list);
+    const GPtrArray *             connections;
+    GSList *                      iter;
+    gboolean                      did_header = FALSE, did_vpn = FALSE;
+    NMEditorConnectionTypeData ** types;
+    NMConnection *                conn, *selected_conn;
+    int                           i, row, selected_row;
+
+    selected_row  = nmt_newt_listbox_get_active(priv->listbox);
+    selected_conn = nmt_newt_listbox_get_active_key(priv->listbox);
+
+    free_connections(list);
+    connections = nm_client_get_connections(nm_client);
+    for (i = 0; i < connections->len; i++) {
+        conn = connections->pdata[i];
+
+        if (priv->connection_filter
+            && !priv->connection_filter(list, conn, priv->connection_filter_data))
+            continue;
+
+        g_signal_connect(conn,
+                         NM_CONNECTION_CHANGED,
+                         G_CALLBACK(rebuild_on_connection_changed),
+                         list);
+        priv->connections = g_slist_prepend(priv->connections, g_object_ref(conn));
+    }
+    priv->connections = g_slist_sort(priv->connections, sort_by_timestamp);
+    g_object_notify(G_OBJECT(list), "connections");
+    g_object_notify(G_OBJECT(list), "num-connections");
+
+    nmt_newt_component_set_sensitive(NMT_NEWT_COMPONENT(priv->edit), priv->connections != NULL);
+    nmt_newt_component_set_sensitive(NMT_NEWT_COMPONENT(priv->delete), priv->connections != NULL);
+
+    nmt_newt_listbox_clear(priv->listbox);
+
+    if (!priv->grouped) {
+        /* Just add the connections in order */
+        for (iter = priv->connections, row = 0; iter; iter = iter->next, row++) {
+            conn = iter->data;
+            nmt_newt_listbox_append(priv->listbox, nm_connection_get_id(conn), conn);
+            if (conn == selected_conn)
+                selected_row = row;
+        }
+        if (selected_row >= row)
+            selected_row = row - 1;
+        nmt_newt_listbox_set_active(priv->listbox, selected_row);
+
+        return;
+    }
+
+    types = nm_editor_utils_get_connection_type_list();
+    for (i = row = 0; types[i]; i++) {
+        if (types[i]->setting_type == NM_TYPE_SETTING_VPN) {
+            if (did_vpn)
+                continue;
+            did_vpn = TRUE;
+        }
+
+        did_header = FALSE;
+
+        for (iter = priv->connections; iter; iter = iter->next) {
+            NMSetting *setting;
+            char *     indented;
+
+            conn    = iter->data;
+            setting = nm_connection_get_setting(conn, types[i]->setting_type);
+            if (!setting)
+                continue;
+            if (!nm_connection_is_type(conn, nm_setting_get_name(setting)))
+                continue;
+
+            if (!did_header) {
+                nmt_newt_listbox_append(priv->listbox, types[i]->name, NULL);
+                if (row == selected_row)
+                    selected_row++;
+                row++;
+                did_header = TRUE;
+            }
+
+            indented = g_strdup_printf("  %s", nm_connection_get_id(conn));
+            nmt_newt_listbox_append(priv->listbox, indented, conn);
+            g_free(indented);
+
+            if (conn == selected_conn)
+                selected_row = row;
+            row++;
+        }
+    }
+
+    if (selected_row >= row)
+        selected_row = row - 1;
+    nmt_newt_listbox_set_active(priv->listbox, selected_row);
+}
+
+static void
+rebuild_on_connections_changed(GObject *object, GParamSpec *pspec, gpointer list)
+{
+    nmt_edit_connection_list_rebuild(list);
+}
+
+static void
+nmt_edit_connection_list_constructed(GObject *object)
+{
+    NmtEditConnectionList *       list = NMT_EDIT_CONNECTION_LIST(object);
+    NmtEditConnectionListPrivate *priv = NMT_EDIT_CONNECTION_LIST_GET_PRIVATE(list);
+
+    if (priv->extra)
+        nmt_newt_button_box_add_widget_end(priv->buttons, priv->extra);
+
+    g_signal_connect(nm_client,
+                     "notify::" NM_CLIENT_CONNECTIONS,
+                     G_CALLBACK(rebuild_on_connections_changed),
+                     list);
+
+    nmt_edit_connection_list_rebuild(list);
+
+    G_OBJECT_CLASS(nmt_edit_connection_list_parent_class)->constructed(object);
+}
+
+static void
+add_clicked(NmtNewtButton *button, gpointer list)
+{
+    g_signal_emit(list, signals[ADD_CONNECTION], 0);
+}
+
+static void
+edit_clicked(NmtNewtButton *button, gpointer list)
+{
+    NmtEditConnectionListPrivate *priv = NMT_EDIT_CONNECTION_LIST_GET_PRIVATE(list);
+    NMConnection *                connection;
+
+    connection = nmt_newt_listbox_get_active_key(priv->listbox);
+    g_return_if_fail(connection != NULL);
+
+    g_signal_emit(list, signals[EDIT_CONNECTION], 0, connection);
+}
+
+static void
+delete_clicked(NmtNewtButton *button, gpointer list)
+{
+    NmtEditConnectionListPrivate *priv = NMT_EDIT_CONNECTION_LIST_GET_PRIVATE(list);
+    NMRemoteConnection *          connection;
+
+    connection = nmt_newt_listbox_get_active_key(priv->listbox);
+    g_return_if_fail(connection != NULL);
+
+    g_signal_emit(list, signals[REMOVE_CONNECTION], 0, connection);
+}
+
+static void
+listbox_activated(NmtNewtWidget *listbox, gpointer list)
+{
+    NmtEditConnectionListPrivate *priv = NMT_EDIT_CONNECTION_LIST_GET_PRIVATE(list);
+
+    edit_clicked(NMT_NEWT_BUTTON(priv->edit), list);
+}
+
+static void
+connection_saved(GObject *conn, GAsyncResult *result, gpointer user_data)
+{
+    nm_remote_connection_save_finish(NM_REMOTE_CONNECTION(conn), result, NULL);
+}
+
+void
+nmt_edit_connection_list_recommit(NmtEditConnectionList *list)
+{
+    NmtEditConnectionListPrivate *priv = NMT_EDIT_CONNECTION_LIST_GET_PRIVATE(list);
+    NMConnection *                conn;
+    GSList *                      iter;
+
+    for (iter = priv->connections; iter; iter = iter->next) {
+        conn = iter->data;
+
+        if (NM_IS_REMOTE_CONNECTION(conn)
+            && (nm_remote_connection_get_unsaved(NM_REMOTE_CONNECTION(conn)) == FALSE)) {
+            nm_remote_connection_save_async(NM_REMOTE_CONNECTION(conn),
+                                            NULL,
+                                            connection_saved,
+                                            NULL);
+        }
+    }
+}
+
+static void
+nmt_edit_connection_list_finalize(GObject *object)
+{
+    NmtEditConnectionListPrivate *priv = NMT_EDIT_CONNECTION_LIST_GET_PRIVATE(object);
+
+    free_connections(NMT_EDIT_CONNECTION_LIST(object));
+    g_clear_object(&priv->extra);
+
+    G_OBJECT_CLASS(nmt_edit_connection_list_parent_class)->finalize(object);
+}
+
+static void
+nmt_edit_connection_list_set_property(GObject *     object,
+                                      guint         prop_id,
+                                      const GValue *value,
+                                      GParamSpec *  pspec)
+{
+    NmtEditConnectionListPrivate *priv = NMT_EDIT_CONNECTION_LIST_GET_PRIVATE(object);
+
+    switch (prop_id) {
+    case PROP_GROUPED:
+        priv->grouped = g_value_get_boolean(value);
+        break;
+    case PROP_CONNECTION_FILTER:
+        priv->connection_filter = g_value_get_pointer(value);
+        break;
+    case PROP_CONNECTION_FILTER_DATA:
+        priv->connection_filter_data = g_value_get_pointer(value);
+        break;
+    case PROP_EXTRA_WIDGET:
+        priv->extra = g_value_get_object(value);
+        if (priv->extra)
+            g_object_ref_sink(priv->extra);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nmt_edit_connection_list_get_property(GObject *   object,
+                                      guint       prop_id,
+                                      GValue *    value,
+                                      GParamSpec *pspec)
+{
+    NmtEditConnectionListPrivate *priv = NMT_EDIT_CONNECTION_LIST_GET_PRIVATE(object);
+    GPtrArray *                   connections;
+    GSList *                      iter;
+
+    switch (prop_id) {
+    case PROP_GROUPED:
+        g_value_set_boolean(value, priv->grouped);
+        break;
+    case PROP_CONNECTION_FILTER:
+        g_value_set_pointer(value, priv->connection_filter);
+        break;
+    case PROP_CONNECTION_FILTER_DATA:
+        g_value_set_pointer(value, priv->connection_filter_data);
+        break;
+    case PROP_EXTRA_WIDGET:
+        g_value_set_object(value, priv->extra);
+        break;
+    case PROP_CONNECTIONS:
+        connections = g_ptr_array_new_with_free_func(g_object_unref);
+        for (iter = priv->connections; iter; iter = iter->next)
+            g_ptr_array_add(connections, g_object_ref(iter->data));
+        g_value_take_boxed(value, connections);
+        break;
+    case PROP_NUM_CONNECTIONS:
+        g_value_set_int(value, g_slist_length(priv->connections));
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nmt_edit_connection_list_class_init(NmtEditConnectionListClass *list_class)
+{
+    GObjectClass *object_class = G_OBJECT_CLASS(list_class);
+
+    g_type_class_add_private(list_class, sizeof(NmtEditConnectionListPrivate));
+
+    /* virtual methods */
+    object_class->constructed  = nmt_edit_connection_list_constructed;
+    object_class->set_property = nmt_edit_connection_list_set_property;
+    object_class->get_property = nmt_edit_connection_list_get_property;
+    object_class->finalize     = nmt_edit_connection_list_finalize;
+
+    /* signals */
+
+    /**
+     * NmtEditConnectionList::add-connection:
+     * @list: the #NmtEditConnectionList
+     *
+     * Emitted when the user clicks the list's "Add" button.
+     */
+    signals[ADD_CONNECTION] =
+        g_signal_new("add-connection",
+                     G_OBJECT_CLASS_TYPE(object_class),
+                     G_SIGNAL_RUN_FIRST,
+                     G_STRUCT_OFFSET(NmtEditConnectionListClass, add_connection),
+                     NULL,
+                     NULL,
+                     NULL,
+                     G_TYPE_NONE,
+                     0);
+
+    /**
+     * NmtEditConnectionList::edit-connection:
+     * @list: the #NmtEditConnectionList
+     * @connection: the connection to edit
+     *
+     * Emitted when the user clicks the list's "Edit" button, or
+     * hits "Return" on the listbox.
+     */
+    signals[EDIT_CONNECTION] =
+        g_signal_new("edit-connection",
+                     G_OBJECT_CLASS_TYPE(object_class),
+                     G_SIGNAL_RUN_FIRST,
+                     G_STRUCT_OFFSET(NmtEditConnectionListClass, edit_connection),
+                     NULL,
+                     NULL,
+                     NULL,
+                     G_TYPE_NONE,
+                     1,
+                     NM_TYPE_CONNECTION);
+
+    /**
+     * NmtEditConnectionList::remove-connection:
+     * @list: the #NmtEditConnectionList
+     * @connection: the connection to remove
+     *
+     * Emitted when the user clicks the list's "Delete" button.
+     */
+    signals[REMOVE_CONNECTION] =
+        g_signal_new("remove-connection",
+                     G_OBJECT_CLASS_TYPE(object_class),
+                     G_SIGNAL_RUN_FIRST,
+                     G_STRUCT_OFFSET(NmtEditConnectionListClass, remove_connection),
+                     NULL,
+                     NULL,
+                     NULL,
+                     G_TYPE_NONE,
+                     1,
+                     NM_TYPE_CONNECTION);
+
+    /* properties */
+
+    /**
+     * NmtEditConnectionList:grouped:
+     *
+     * If %TRUE, connections should be grouped by type, with headers
+     * indicating the types (as in the main connection list). If %FALSE,
+     * they will not be grouped (as in slave connection lists).
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_GROUPED,
+        g_param_spec_boolean("grouped",
+                             "",
+                             "",
+                             TRUE,
+                             G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+    /**
+     * NmtEditConnectionListFilter:
+     * @list: the #NmtEditConnectionList
+     * @connection: an #NMConnection
+     * @user_data: the user data
+     *
+     * Decides whether @connection should be displayed in @list.
+     *
+     * Returns: %TRUE or %FALSE
+     */
+    /**
+     * NmtEditConnectionList:connection-filter:
+     *
+     * A callback function for filtering which connections appear in
+     * the list.
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_CONNECTION_FILTER,
+        g_param_spec_pointer("connection-filter",
+                             "",
+                             "",
+                             G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+    /**
+     * NmtEditConnectionList:connection-filter-data:
+     *
+     * Data for the #NmtEditConnectionList:connection-filter.
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_CONNECTION_FILTER_DATA,
+        g_param_spec_pointer("connection-filter-data",
+                             "",
+                             "",
+                             G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+    /**
+     * NmtEditConnectionList:extra-widget:
+     *
+     * An extra button widget to display at the bottom of the button
+     * box.
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_EXTRA_WIDGET,
+        g_param_spec_object("extra-widget",
+                            "",
+                            "",
+                            NMT_TYPE_NEWT_WIDGET,
+                            G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+    /**
+     * NmtEditConnectionList:connections:
+     *
+     * The list of connections in the widget.
+     *
+     * Element-Type: #NMConnection
+     */
+    g_object_class_install_property(object_class,
+                                    PROP_CONNECTIONS,
+                                    g_param_spec_boxed("connections",
+                                                       "",
+                                                       "",
+                                                       G_TYPE_PTR_ARRAY,
+                                                       G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+    /**
+     * NmtEditConnectionList:num-connections:
+     *
+     * The number of connections in the widget.
+     */
+    g_object_class_install_property(object_class,
+                                    PROP_NUM_CONNECTIONS,
+                                    g_param_spec_int("num-connections",
+                                                     "",
+                                                     "",
+                                                     0,
+                                                     G_MAXINT,
+                                                     0,
+                                                     G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+}
diff --git a/src/nmtui/nmt-edit-connection-list.h b/src/nmtui/nmt-edit-connection-list.h
new file mode 100644
index 00000000..824f767a
--- /dev/null
+++ b/src/nmtui/nmt-edit-connection-list.h
@@ -0,0 +1,45 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+#ifndef NMT_EDIT_CONNECTION_LIST_H
+#define NMT_EDIT_CONNECTION_LIST_H
+
+#include "libnmt-newt/nmt-newt.h"
+
+#define NMT_TYPE_EDIT_CONNECTION_LIST (nmt_edit_connection_list_get_type())
+#define NMT_EDIT_CONNECTION_LIST(obj) \
+    (G_TYPE_CHECK_INSTANCE_CAST((obj), NMT_TYPE_EDIT_CONNECTION_LIST, NmtEditConnectionList))
+#define NMT_EDIT_CONNECTION_LIST_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NMT_TYPE_EDIT_CONNECTION_LIST, NmtEditConnectionListClass))
+#define NMT_IS_EDIT_CONNECTION_LIST(obj) \
+    (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMT_TYPE_EDIT_CONNECTION_LIST))
+#define NMT_IS_EDIT_CONNECTION_LIST_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_TYPE((klass), NMT_TYPE_EDIT_CONNECTION_LIST))
+#define NMT_EDIT_CONNECTION_LIST_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NMT_TYPE_EDIT_CONNECTION_LIST, NmtEditConnectionListClass))
+
+typedef struct {
+    NmtNewtGrid parent;
+
+} NmtEditConnectionList;
+
+typedef struct {
+    NmtNewtGridClass parent;
+
+    /* signals */
+    void (*add_connection)(NmtEditConnectionList *list);
+    void (*edit_connection)(NmtEditConnectionList *list, NMConnection *connection);
+    void (*remove_connection)(NmtEditConnectionList *list, NMRemoteConnection *connection);
+} NmtEditConnectionListClass;
+
+GType nmt_edit_connection_list_get_type(void);
+
+typedef gboolean (*NmtEditConnectionListFilter)(NmtEditConnectionList *list,
+                                                NMConnection *         connection,
+                                                gpointer               user_data);
+
+void nmt_edit_connection_list_recommit(NmtEditConnectionList *list);
+
+#endif /* NMT_EDIT_CONNECTION_LIST_H */
diff --git a/src/nmtui/nmt-editor-grid.c b/src/nmtui/nmt-editor-grid.c
new file mode 100644
index 00000000..8c79e8d9
--- /dev/null
+++ b/src/nmtui/nmt-editor-grid.c
@@ -0,0 +1,441 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+/**
+ * SECTION:nmt-editor-grid
+ * @short_description: Grid widget for #NmtEditorPages
+ *
+ * #NmtEditorGrid is the layout grid used by #NmtEditorPages. It
+ * consists of a number of rows, each containing either a single
+ * widget that spans the entire width of the row, or else containing a
+ * label, a widget, and an optional extra widget.
+ *
+ * Each row of the grid can take up multiple on-screen rows, if
+ * its main widget is multiple rows high. The label and extra widgets
+ * will be top-aligned if the row is taller than they are.
+ *
+ * The #NmtEditorGrids in a form behave as though they are all in a
+ * "size group" together; they will all use the same column widths,
+ * which will be wide enough for the widest labels/widgets in any of
+ * the grids. #NmtEditorGrid is also specially aware of #NmtNewtSection,
+ * and grids inside sections will automatically take the size of the
+ * section border into account as well.
+ */
+
+#include "libnm-client-aux-extern/nm-default-client.h"
+
+#include "nmt-editor-grid.h"
+
+G_DEFINE_TYPE(NmtEditorGrid, nmt_editor_grid, NMT_TYPE_NEWT_CONTAINER)
+
+#define NMT_EDITOR_GRID_GET_PRIVATE(o) \
+    (G_TYPE_INSTANCE_GET_PRIVATE((o), NMT_TYPE_EDITOR_GRID, NmtEditorGridPrivate))
+
+typedef struct {
+    GArray *rows;
+    int *   row_heights;
+    int     indent;
+} NmtEditorGridPrivate;
+
+typedef struct {
+    NmtNewtWidget *       label;
+    NmtNewtWidget *       widget;
+    NmtNewtWidget *       extra;
+    NmtEditorGridRowFlags flags;
+} NmtEditorGridRow;
+
+typedef struct {
+    int col_widths[3];
+} NmtEditorGridFormState;
+
+/**
+ * nmt_editor_grid_new:
+ *
+ * Creates a new #NmtEditorGrid
+ *
+ * Returns: a new #NmtEditorGrid
+ */
+NmtNewtWidget *
+nmt_editor_grid_new(void)
+{
+    return g_object_new(NMT_TYPE_EDITOR_GRID, NULL);
+}
+
+static void
+nmt_editor_grid_init(NmtEditorGrid *grid)
+{
+    NmtEditorGridPrivate *priv = NMT_EDITOR_GRID_GET_PRIVATE(grid);
+
+    priv->rows = g_array_new(FALSE, TRUE, sizeof(NmtEditorGridRow));
+}
+
+static void
+nmt_editor_grid_finalize(GObject *object)
+{
+    NmtEditorGridPrivate *priv = NMT_EDITOR_GRID_GET_PRIVATE(object);
+
+    g_array_unref(priv->rows);
+    nm_clear_g_free(&priv->row_heights);
+
+    G_OBJECT_CLASS(nmt_editor_grid_parent_class)->finalize(object);
+}
+
+/**
+ * nmt_editor_grid_append:
+ * @grid: the #NmtEditorGrid
+ * @label: (allow-none): the label text for @widget, or %NULL
+ * @widget: (allow-none): the (main) widget
+ * @extra: (allow-none): optional extra widget
+ *
+ * Adds a row to @grid.
+ *
+ * If @label and @widget are both non-%NULL, this will add a three-column row,
+ * containing a right-aligned #NmtNewtLabel in the first column, @widget in the
+ * second column, and @extra (if non-%NULL) in the third column.
+ *
+ * If either @label or @widget is %NULL, then the other column will expand into
+ * it.
+ *
+ * See also nmt_editor_grid_set_row_flags().
+ */
+void
+nmt_editor_grid_append(NmtEditorGrid *grid,
+                       const char *   label,
+                       NmtNewtWidget *widget,
+                       NmtNewtWidget *extra)
+{
+    NmtEditorGridPrivate * priv         = NMT_EDITOR_GRID_GET_PRIVATE(grid);
+    NmtNewtContainerClass *parent_class = NMT_NEWT_CONTAINER_CLASS(nmt_editor_grid_parent_class);
+    NmtNewtContainer *     container    = NMT_NEWT_CONTAINER(grid);
+    NmtEditorGridRow       row;
+
+    g_return_if_fail(label != NULL || widget != NULL);
+
+    memset(&row, 0, sizeof(row));
+
+    if (label && !widget) {
+        widget = nmt_newt_label_new(label);
+        label  = NULL;
+    }
+
+    if (label) {
+        row.label = nmt_newt_label_new(label);
+        parent_class->add(container, row.label);
+    }
+
+    row.widget = widget;
+    parent_class->add(container, widget);
+    if (row.label) {
+        g_object_bind_property(row.widget,
+                               "valid",
+                               row.label,
+                               "highlight",
+                               G_BINDING_INVERT_BOOLEAN | G_BINDING_SYNC_CREATE);
+    }
+
+    if (extra) {
+        row.extra = extra;
+        parent_class->add(container, extra);
+    }
+
+    g_array_append_val(priv->rows, row);
+}
+
+static int
+nmt_editor_grid_find_widget(NmtEditorGrid *grid, NmtNewtWidget *widget)
+{
+    NmtEditorGridPrivate *priv = NMT_EDITOR_GRID_GET_PRIVATE(grid);
+    NmtEditorGridRow *    rows = (NmtEditorGridRow *) priv->rows->data;
+    int                   i;
+
+    for (i = 0; i < priv->rows->len; i++) {
+        if (rows[i].label == widget || rows[i].widget == widget || rows[i].extra == widget)
+            return i;
+    }
+
+    return -1;
+}
+
+/**
+ * NmtEditorGridRowFlags:
+ * @NMT_EDITOR_GRID_ROW_LABEL_ALIGN_LEFT: the row's label should be
+ *   aligned left instead of right.
+ * @NMT_EDITOR_GRID_ROW_EXTRA_ALIGN_RIGHT: the row's extra widget
+ *   should be aligned right instead of left.
+ *
+ * Flags to alter an #NmtEditorGrid row's layout.
+ */
+
+/**
+ * nmt_editor_grid_set_row_flags:
+ * @grid: an #NmtEditorGrid
+ * @widget: the widget whose row you want to adjust
+ * @flags: the flags to set
+ *
+ * Sets flags to adjust the layout of @widget's row in @grid.
+ */
+void
+nmt_editor_grid_set_row_flags(NmtEditorGrid *       grid,
+                              NmtNewtWidget *       widget,
+                              NmtEditorGridRowFlags flags)
+{
+    NmtEditorGridPrivate *priv = NMT_EDITOR_GRID_GET_PRIVATE(grid);
+    NmtEditorGridRow *    rows = (NmtEditorGridRow *) priv->rows->data;
+    int                   i;
+
+    i = nmt_editor_grid_find_widget(grid, widget);
+    if (i != -1)
+        rows[i].flags = flags;
+}
+
+static void
+nmt_editor_grid_remove(NmtNewtContainer *container, NmtNewtWidget *widget)
+{
+    NmtEditorGrid *        grid         = NMT_EDITOR_GRID(container);
+    NmtEditorGridPrivate * priv         = NMT_EDITOR_GRID_GET_PRIVATE(grid);
+    NmtNewtContainerClass *parent_class = NMT_NEWT_CONTAINER_CLASS(nmt_editor_grid_parent_class);
+    NmtEditorGridRow *     rows         = (NmtEditorGridRow *) priv->rows->data;
+    int                    i;
+
+    i = nmt_editor_grid_find_widget(grid, widget);
+    if (i != -1) {
+        if (rows[i].label)
+            parent_class->remove(container, rows[i].label);
+        parent_class->remove(container, rows[i].widget);
+        if (rows[i].extra)
+            parent_class->remove(container, rows[i].extra);
+
+        g_array_remove_index(priv->rows, i);
+        return;
+    }
+
+    // FIXME: shouldn't happen
+    parent_class->remove(container, widget);
+}
+
+static newtComponent *
+nmt_editor_grid_get_components(NmtNewtWidget *widget)
+{
+    NmtEditorGridPrivate *priv = NMT_EDITOR_GRID_GET_PRIVATE(widget);
+    NmtEditorGridRow *    rows = (NmtEditorGridRow *) priv->rows->data;
+    newtComponent *       child_cos;
+    GPtrArray *           cos;
+    int                   i, c;
+
+    cos = g_ptr_array_new();
+
+    for (i = 0; i < priv->rows->len; i++) {
+        if (!nmt_newt_widget_get_visible(rows[i].widget))
+            continue;
+
+        if (rows[i].label) {
+            child_cos = nmt_newt_widget_get_components(rows[i].label);
+            g_assert(child_cos[0] && !child_cos[1]);
+            g_ptr_array_add(cos, child_cos[0]);
+            g_free(child_cos);
+        }
+
+        child_cos = nmt_newt_widget_get_components(rows[i].widget);
+        for (c = 0; child_cos[c]; c++)
+            g_ptr_array_add(cos, child_cos[c]);
+        g_free(child_cos);
+
+        if (rows[i].extra) {
+            child_cos = nmt_newt_widget_get_components(rows[i].extra);
+            for (c = 0; child_cos[c]; c++)
+                g_ptr_array_add(cos, child_cos[c]);
+            g_free(child_cos);
+        }
+    }
+
+    g_ptr_array_add(cos, NULL);
+    return (newtComponent *) g_ptr_array_free(cos, FALSE);
+}
+
+static NmtEditorGridFormState *
+get_form_state(NmtNewtWidget *widget)
+{
+    NmtNewtForm *           form = nmt_newt_widget_get_form(widget);
+    NmtEditorGridFormState *state;
+
+    if (!form)
+        return NULL;
+
+    state = g_object_get_data(G_OBJECT(form), "NmtEditorGridFormState");
+    if (state)
+        return state;
+
+    state = g_new0(NmtEditorGridFormState, 1);
+    g_object_set_data_full(G_OBJECT(form), "NmtEditorGridFormState", state, g_free);
+    return state;
+}
+
+static void
+nmt_editor_grid_realize(NmtNewtWidget *widget)
+{
+    NmtEditorGridPrivate *priv = NMT_EDITOR_GRID_GET_PRIVATE(widget);
+    NmtNewtWidget *       parent;
+
+    NMT_NEWT_WIDGET_CLASS(nmt_editor_grid_parent_class)->realize(widget);
+
+    /* This is a hack, but it's the simplest way to make it work... */
+    priv->indent = 0;
+
+    parent = nmt_newt_widget_get_parent(widget);
+    while (parent) {
+        if (NMT_IS_NEWT_SECTION(parent)) {
+            priv->indent = 2;
+            break;
+        }
+        parent = nmt_newt_widget_get_parent(parent);
+    }
+}
+
+static void
+nmt_editor_grid_unrealize(NmtNewtWidget *widget)
+{
+    NmtEditorGridFormState *state = get_form_state(widget);
+
+    if (state)
+        memset(state->col_widths, 0, sizeof(state->col_widths));
+
+    NMT_NEWT_WIDGET_CLASS(nmt_editor_grid_parent_class)->unrealize(widget);
+}
+
+static void
+nmt_editor_grid_size_request(NmtNewtWidget *widget, int *width, int *height)
+{
+    NmtEditorGridPrivate *  priv        = NMT_EDITOR_GRID_GET_PRIVATE(widget);
+    NmtEditorGridRow *      rows        = (NmtEditorGridRow *) priv->rows->data;
+    NmtEditorGridFormState *state       = get_form_state(widget);
+    gboolean                add_padding = FALSE;
+    int                     i;
+
+    g_free(priv->row_heights);
+    priv->row_heights = g_new0(int, priv->rows->len);
+
+    *height = 0;
+    for (i = 0; i < priv->rows->len; i++) {
+        int lwidth, lheight, wwidth, wheight, ewidth, eheight;
+
+        if (!nmt_newt_widget_get_visible(rows[i].widget))
+            continue;
+
+        if (rows[i].label) {
+            nmt_newt_widget_size_request(rows[i].label, &lwidth, &lheight);
+            lwidth += priv->indent;
+            state->col_widths[0] = MAX(state->col_widths[0], lwidth);
+
+            nmt_newt_widget_size_request(rows[i].widget, &wwidth, &wheight);
+            state->col_widths[1] = MAX(state->col_widths[1], wwidth);
+            priv->row_heights[i] = wheight;
+
+            add_padding = TRUE;
+        } else {
+            nmt_newt_widget_size_request(rows[i].widget, &wwidth, &wheight);
+            priv->row_heights[i] = wheight;
+        }
+
+        if (rows[i].extra) {
+            nmt_newt_widget_size_request(rows[i].extra, &ewidth, &eheight);
+            state->col_widths[2] = MAX(state->col_widths[2], ewidth);
+            priv->row_heights[i] = MAX(priv->row_heights[i], eheight);
+        }
+
+        *height += priv->row_heights[i];
+    }
+
+    *width = state->col_widths[0] + state->col_widths[1] + state->col_widths[2];
+    if (add_padding)
+        *width += 2;
+}
+
+static void
+nmt_editor_grid_size_allocate(NmtNewtWidget *widget, int x, int y, int width, int height)
+{
+    NmtEditorGridPrivate *  priv  = NMT_EDITOR_GRID_GET_PRIVATE(widget);
+    NmtEditorGridRow *      rows  = (NmtEditorGridRow *) priv->rows->data;
+    NmtEditorGridFormState *state = get_form_state(widget);
+    int                     col0_width, col1_width, col2_width;
+    int                     i, row;
+
+    col0_width = state->col_widths[0] - priv->indent;
+    col1_width = state->col_widths[1];
+    col2_width = state->col_widths[2];
+
+    for (i = row = 0; i < priv->rows->len; i++) {
+        if (!nmt_newt_widget_get_visible(rows[i].widget))
+            continue;
+
+        if (rows[i].label) {
+            int lwidth, lheight, lx;
+
+            if (rows[i].flags & NMT_EDITOR_GRID_ROW_LABEL_ALIGN_LEFT)
+                lx = x;
+            else {
+                nmt_newt_widget_size_request(rows[i].label, &lwidth, &lheight);
+                lx = x + col0_width - lwidth;
+            }
+
+            nmt_newt_widget_size_allocate(rows[i].label,
+                                          lx,
+                                          y + row,
+                                          col0_width,
+                                          priv->row_heights[i]);
+
+            nmt_newt_widget_size_allocate(rows[i].widget,
+                                          x + col0_width + 1,
+                                          y + row,
+                                          col1_width,
+                                          priv->row_heights[i]);
+        } else {
+            nmt_newt_widget_size_allocate(rows[i].widget,
+                                          x,
+                                          y + row,
+                                          col0_width + col1_width + 1,
+                                          priv->row_heights[i]);
+        }
+
+        if (rows[i].extra) {
+            int wwidth, wheight, ex;
+
+            if (rows[i].flags & NMT_EDITOR_GRID_ROW_EXTRA_ALIGN_RIGHT)
+                ex = x + col0_width + col1_width + 2;
+            else {
+                nmt_newt_widget_size_request(rows[i].widget, &wwidth, &wheight);
+                ex = x + col0_width + wwidth + 2;
+            }
+
+            nmt_newt_widget_size_allocate(rows[i].extra,
+                                          ex,
+                                          y + row,
+                                          col2_width,
+                                          priv->row_heights[i]);
+        }
+
+        row += priv->row_heights[i];
+    }
+}
+
+static void
+nmt_editor_grid_class_init(NmtEditorGridClass *grid_class)
+{
+    GObjectClass *         object_class    = G_OBJECT_CLASS(grid_class);
+    NmtNewtWidgetClass *   widget_class    = NMT_NEWT_WIDGET_CLASS(grid_class);
+    NmtNewtContainerClass *container_class = NMT_NEWT_CONTAINER_CLASS(grid_class);
+
+    g_type_class_add_private(grid_class, sizeof(NmtEditorGridPrivate));
+
+    /* virtual methods */
+    object_class->finalize = nmt_editor_grid_finalize;
+
+    widget_class->realize        = nmt_editor_grid_realize;
+    widget_class->unrealize      = nmt_editor_grid_unrealize;
+    widget_class->get_components = nmt_editor_grid_get_components;
+    widget_class->size_request   = nmt_editor_grid_size_request;
+    widget_class->size_allocate  = nmt_editor_grid_size_allocate;
+
+    container_class->remove = nmt_editor_grid_remove;
+}
diff --git a/src/nmtui/nmt-editor-grid.h b/src/nmtui/nmt-editor-grid.h
new file mode 100644
index 00000000..39a86067
--- /dev/null
+++ b/src/nmtui/nmt-editor-grid.h
@@ -0,0 +1,48 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+#ifndef NMT_EDITOR_GRID_H
+#define NMT_EDITOR_GRID_H
+
+#include "libnmt-newt/nmt-newt.h"
+
+#define NMT_TYPE_EDITOR_GRID (nmt_editor_grid_get_type())
+#define NMT_EDITOR_GRID(obj) \
+    (G_TYPE_CHECK_INSTANCE_CAST((obj), NMT_TYPE_EDITOR_GRID, NmtEditorGrid))
+#define NMT_EDITOR_GRID_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NMT_TYPE_EDITOR_GRID, NmtEditorGridClass))
+#define NMT_IS_EDITOR_GRID(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMT_TYPE_EDITOR_GRID))
+#define NMT_IS_EDITOR_GRID_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NMT_TYPE_EDITOR_GRID))
+#define NMT_EDITOR_GRID_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NMT_TYPE_EDITOR_GRID, NmtEditorGridClass))
+
+typedef struct {
+    NmtNewtContainer parent;
+
+} NmtEditorGrid;
+
+typedef struct {
+    NmtNewtContainerClass parent;
+
+} NmtEditorGridClass;
+
+GType nmt_editor_grid_get_type(void);
+
+typedef enum {
+    NMT_EDITOR_GRID_ROW_LABEL_ALIGN_LEFT  = (1 << 0),
+    NMT_EDITOR_GRID_ROW_EXTRA_ALIGN_RIGHT = (1 << 1)
+} NmtEditorGridRowFlags;
+
+NmtNewtWidget *nmt_editor_grid_new(void);
+
+void nmt_editor_grid_append(NmtEditorGrid *grid,
+                            const char *   label,
+                            NmtNewtWidget *widget,
+                            NmtNewtWidget *extra);
+void nmt_editor_grid_set_row_flags(NmtEditorGrid *       grid,
+                                   NmtNewtWidget *       widget,
+                                   NmtEditorGridRowFlags flags);
+
+#endif /* NMT_EDITOR_GRID_H */
diff --git a/src/nmtui/nmt-editor-page-device.c b/src/nmtui/nmt-editor-page-device.c
new file mode 100644
index 00000000..950f4db1
--- /dev/null
+++ b/src/nmtui/nmt-editor-page-device.c
@@ -0,0 +1,119 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+/**
+ * SECTION:nmt-editor-page-device
+ * @short_description: Abstract base class for "device" editor pages
+ *
+ * #NmtEditorPageDevice is the base class for #NmtEditorPage subclasses
+ * representing device-type-specific data. (Eg, #NmtPageEthernet,
+ * #NmtPageVlan, etc).
+ *
+ * FIXME: rename to NmtEditorPageDevice, so it doesn't sound like it's
+ * an actual page type.
+ */
+
+#include "libnm-client-aux-extern/nm-default-client.h"
+
+#include "nmt-editor-page-device.h"
+
+G_DEFINE_TYPE(NmtEditorPageDevice, nmt_editor_page_device, NMT_TYPE_EDITOR_PAGE)
+
+#define NMT_EDITOR_PAGE_DEVICE_GET_PRIVATE(o) \
+    (G_TYPE_INSTANCE_GET_PRIVATE((o), NMT_TYPE_EDITOR_PAGE_DEVICE, NmtEditorPageDevicePrivate))
+
+typedef struct {
+    NmtDeviceEntry *device_entry;
+    gboolean        show_by_default;
+} NmtEditorPageDevicePrivate;
+
+enum {
+    PROP_0,
+
+    PROP_DEVICE_ENTRY,
+
+    LAST_PROP
+};
+
+static void
+nmt_editor_page_device_init(NmtEditorPageDevice *device)
+{}
+
+static void
+nmt_editor_page_device_finalize(GObject *object)
+{
+    NmtEditorPageDevicePrivate *priv = NMT_EDITOR_PAGE_DEVICE_GET_PRIVATE(object);
+
+    g_clear_object(&priv->device_entry);
+
+    G_OBJECT_CLASS(nmt_editor_page_device_parent_class)->finalize(object);
+}
+
+NmtDeviceEntry *
+nmt_editor_page_device_get_device_entry(NmtEditorPageDevice *page)
+{
+    NmtEditorPageDevicePrivate *priv = NMT_EDITOR_PAGE_DEVICE_GET_PRIVATE(page);
+
+    return priv->device_entry;
+}
+
+static void
+nmt_editor_page_device_set_property(GObject *     object,
+                                    guint         prop_id,
+                                    const GValue *value,
+                                    GParamSpec *  pspec)
+{
+    NmtEditorPageDevicePrivate *priv = NMT_EDITOR_PAGE_DEVICE_GET_PRIVATE(object);
+
+    switch (prop_id) {
+    case PROP_DEVICE_ENTRY:
+        priv->device_entry = g_value_dup_object(value);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nmt_editor_page_device_get_property(GObject *   object,
+                                    guint       prop_id,
+                                    GValue *    value,
+                                    GParamSpec *pspec)
+{
+    NmtEditorPageDevicePrivate *priv = NMT_EDITOR_PAGE_DEVICE_GET_PRIVATE(object);
+
+    switch (prop_id) {
+    case PROP_DEVICE_ENTRY:
+        g_value_set_object(value, priv->device_entry);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nmt_editor_page_device_class_init(NmtEditorPageDeviceClass *page_device_class)
+{
+    GObjectClass *object_class = G_OBJECT_CLASS(page_device_class);
+
+    g_type_class_add_private(page_device_class, sizeof(NmtEditorPageDevicePrivate));
+
+    /* virtual methods */
+    object_class->set_property = nmt_editor_page_device_set_property;
+    object_class->get_property = nmt_editor_page_device_get_property;
+    object_class->finalize     = nmt_editor_page_device_finalize;
+
+    /* properties */
+    g_object_class_install_property(
+        object_class,
+        PROP_DEVICE_ENTRY,
+        g_param_spec_object("device-entry",
+                            "",
+                            "",
+                            NMT_TYPE_DEVICE_ENTRY,
+                            G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+}
diff --git a/src/nmtui/nmt-editor-page-device.h b/src/nmtui/nmt-editor-page-device.h
new file mode 100644
index 00000000..c8dbfad9
--- /dev/null
+++ b/src/nmtui/nmt-editor-page-device.h
@@ -0,0 +1,38 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+#ifndef NMT_EDITOR_PAGE_DEVICE_H
+#define NMT_EDITOR_PAGE_DEVICE_H
+
+#include "nmt-editor-page.h"
+#include "nmt-device-entry.h"
+
+#define NMT_TYPE_EDITOR_PAGE_DEVICE (nmt_editor_page_device_get_type())
+#define NMT_EDITOR_PAGE_DEVICE(obj) \
+    (G_TYPE_CHECK_INSTANCE_CAST((obj), NMT_TYPE_EDITOR_PAGE_DEVICE, NmtEditorPageDevice))
+#define NMT_EDITOR_PAGE_DEVICE_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NMT_TYPE_EDITOR_PAGE_DEVICE, NmtEditorPageDeviceClass))
+#define NMT_IS_EDITOR_PAGE_DEVICE(obj) \
+    (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMT_TYPE_EDITOR_PAGE_DEVICE))
+#define NMT_IS_EDITOR_PAGE_DEVICE_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_TYPE((klass), NMT_TYPE_EDITOR_PAGE_DEVICE))
+#define NMT_EDITOR_PAGE_DEVICE_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NMT_TYPE_EDITOR_PAGE_DEVICE, NmtEditorPageDeviceClass))
+
+typedef struct {
+    NmtEditorPage parent;
+
+} NmtEditorPageDevice;
+
+typedef struct {
+    NmtEditorPageClass parent;
+
+} NmtEditorPageDeviceClass;
+
+GType nmt_editor_page_device_get_type(void);
+
+NmtDeviceEntry *nmt_editor_page_device_get_device_entry(NmtEditorPageDevice *page);
+
+#endif /* NMT_EDITOR_PAGE_DEVICE_H */
diff --git a/src/nmtui/nmt-editor-page.c b/src/nmtui/nmt-editor-page.c
new file mode 100644
index 00000000..6d90d71e
--- /dev/null
+++ b/src/nmtui/nmt-editor-page.c
@@ -0,0 +1,175 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+/**
+ * SECTION:nmt-editor-page:
+ * @short_description: An #NmtEditor "page"
+ *
+ * #NmtEditorPage is the abstract base class for #NmtEditor "pages".
+ * A "page" is a set of related #NmtEditorSections.
+ */
+
+#include "libnm-client-aux-extern/nm-default-client.h"
+
+#include "nmt-editor-page.h"
+
+G_DEFINE_ABSTRACT_TYPE(NmtEditorPage, nmt_editor_page, G_TYPE_OBJECT)
+
+#define NMT_EDITOR_PAGE_GET_PRIVATE(o) \
+    (G_TYPE_INSTANCE_GET_PRIVATE((o), NMT_TYPE_EDITOR_PAGE, NmtEditorPagePrivate))
+
+typedef struct {
+    NMConnection *connection;
+    GSList *      sections;
+
+} NmtEditorPagePrivate;
+
+enum {
+    PROP_0,
+
+    PROP_CONNECTION,
+
+    LAST_PROP
+};
+
+static void
+nmt_editor_page_init(NmtEditorPage *page)
+{}
+
+static void
+nmt_editor_page_finalize(GObject *object)
+{
+    NmtEditorPagePrivate *priv = NMT_EDITOR_PAGE_GET_PRIVATE(object);
+
+    g_clear_object(&priv->connection);
+    g_slist_free_full(priv->sections, g_object_unref);
+
+    G_OBJECT_CLASS(nmt_editor_page_parent_class)->finalize(object);
+}
+
+/**
+ * nmt_editor_page_get_connection:
+ * @page: the #NmtEditorPage
+ *
+ * Gets the page's #NMConnection.
+ *
+ * Returns: (transfer none): the page's #NMConnection.
+ */
+NMConnection *
+nmt_editor_page_get_connection(NmtEditorPage *page)
+{
+    NmtEditorPagePrivate *priv = NMT_EDITOR_PAGE_GET_PRIVATE(page);
+
+    return priv->connection;
+}
+
+/**
+ * nmt_editor_page_get_sections:
+ * @page: the #NmtEditorPage
+ *
+ * Gets the page's list of sections to display.
+ *
+ * Returns: (transfer none): the list of sections; this is the internal list
+ * used by the page and must not be modified or freed.
+ */
+GSList *
+nmt_editor_page_get_sections(NmtEditorPage *page)
+{
+    NmtEditorPagePrivate *priv = NMT_EDITOR_PAGE_GET_PRIVATE(page);
+
+    return priv->sections;
+}
+
+/**
+ * nmt_editor_page_add_section:
+ * @page: the #NmtEditorPage
+ * @section: the #NmtEditorSection
+ *
+ * Adds a section to the page. This should only be called by #NmtEditorPage
+ * subclasses.
+ */
+void
+nmt_editor_page_add_section(NmtEditorPage *page, NmtEditorSection *section)
+{
+    NmtEditorPagePrivate *priv = NMT_EDITOR_PAGE_GET_PRIVATE(page);
+
+    priv->sections = g_slist_append(priv->sections, g_object_ref_sink(section));
+}
+
+/**
+ * nmt_editor_page_saved:
+ * @page: the #NmtEditorPage
+ *
+ * This method is called when the user saves the connection. It gives
+ * the page a chance to do save its data outside the connections (such as
+ * recommit the slave connections).
+ */
+void
+nmt_editor_page_saved(NmtEditorPage *page)
+{
+    NmtEditorPageClass *editor_page_class = NMT_EDITOR_PAGE_GET_CLASS(page);
+
+    if (editor_page_class->saved)
+        editor_page_class->saved(page);
+}
+
+static void
+nmt_editor_page_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+    NmtEditorPagePrivate *priv = NMT_EDITOR_PAGE_GET_PRIVATE(object);
+
+    switch (prop_id) {
+    case PROP_CONNECTION:
+        priv->connection = g_value_dup_object(value);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nmt_editor_page_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+    NmtEditorPagePrivate *priv = NMT_EDITOR_PAGE_GET_PRIVATE(object);
+
+    switch (prop_id) {
+    case PROP_CONNECTION:
+        g_value_set_object(value, priv->connection);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nmt_editor_page_class_init(NmtEditorPageClass *page_class)
+{
+    GObjectClass *object_class = G_OBJECT_CLASS(page_class);
+
+    g_type_class_add_private(page_class, sizeof(NmtEditorPagePrivate));
+
+    /* virtual methods */
+    object_class->set_property = nmt_editor_page_set_property;
+    object_class->get_property = nmt_editor_page_get_property;
+    object_class->finalize     = nmt_editor_page_finalize;
+
+    /* properties */
+
+    /**
+     * NmtEditorPage:connection:
+     *
+     * The page's #NMConnection.
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_CONNECTION,
+        g_param_spec_object("connection",
+                            "",
+                            "",
+                            NM_TYPE_CONNECTION,
+                            G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+}
diff --git a/src/nmtui/nmt-editor-page.h b/src/nmtui/nmt-editor-page.h
new file mode 100644
index 00000000..74b9e09f
--- /dev/null
+++ b/src/nmtui/nmt-editor-page.h
@@ -0,0 +1,44 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 - 2014 Red Hat, Inc.
+ */
+
+#ifndef NMT_EDITOR_PAGE_H
+#define NMT_EDITOR_PAGE_H
+
+#include "nmt-editor-grid.h"
+#include "nmt-editor-section.h"
+
+#define NMT_TYPE_EDITOR_PAGE (nmt_editor_page_get_type())
+#define NMT_EDITOR_PAGE(obj) \
+    (G_TYPE_CHECK_INSTANCE_CAST((obj), NMT_TYPE_EDITOR_PAGE, NmtEditorPage))
+#define NMT_EDITOR_PAGE_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NMT_TYPE_EDITOR_PAGE, NmtEditorPageClass))
+#define NMT_IS_EDITOR_PAGE(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMT_TYPE_EDITOR_PAGE))
+#define NMT_IS_EDITOR_PAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NMT_TYPE_EDITOR_PAGE))
+#define NMT_EDITOR_PAGE_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NMT_TYPE_EDITOR_PAGE, NmtEditorPageClass))
+
+typedef struct {
+    GObject parent;
+
+} NmtEditorPage;
+
+typedef struct {
+    GObjectClass parent;
+
+    void (*saved)(NmtEditorPage *page);
+} NmtEditorPageClass;
+
+GType nmt_editor_page_get_type(void);
+
+NMConnection *nmt_editor_page_get_connection(NmtEditorPage *page);
+
+GSList *nmt_editor_page_get_sections(NmtEditorPage *page);
+
+void nmt_editor_page_saved(NmtEditorPage *page);
+
+/*< protected >*/
+void nmt_editor_page_add_section(NmtEditorPage *page, NmtEditorSection *section);
+
+#endif /* NMT_EDITOR_PAGE_H */
diff --git a/src/nmtui/nmt-editor-section.c b/src/nmtui/nmt-editor-section.c
new file mode 100644
index 00000000..3fbfe7a3
--- /dev/null
+++ b/src/nmtui/nmt-editor-section.c
@@ -0,0 +1,266 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+/**
+ * SECTION:nmt-editor-section:
+ * @short_description: A section of the #NmtEditor
+ *
+ * #NmtEditorSection is the abstract base class for #NmtEditor sections.
+ */
+
+#include "libnm-client-aux-extern/nm-default-client.h"
+
+#include "nmt-editor-section.h"
+#include "libnmt-newt/nmt-newt-toggle-button.h"
+
+G_DEFINE_TYPE(NmtEditorSection, nmt_editor_section, NMT_TYPE_NEWT_SECTION)
+
+#define NMT_EDITOR_SECTION_GET_PRIVATE(o) \
+    (G_TYPE_INSTANCE_GET_PRIVATE((o), NMT_TYPE_EDITOR_SECTION, NmtEditorSectionPrivate))
+
+typedef struct {
+    NmtEditorGrid *header, *body;
+    char *         title;
+    NmtNewtWidget *header_widget;
+    NmtNewtWidget *toggle;
+
+    gboolean show_by_default;
+} NmtEditorSectionPrivate;
+
+enum {
+    PROP_0,
+
+    PROP_TITLE,
+    PROP_SHOW_BY_DEFAULT,
+    PROP_HEADER_WIDGET,
+
+    LAST_PROP
+};
+
+/**
+ * nmt_editor_section_new:
+ * @title: the section title
+ * @header_widget: (allow-none): the widget to show next to the title
+ * @show_by_default: whether the section should be open by default
+ *
+ * Creates a new #NmtEditorSection.
+ *
+ * Returns: a new #NmtEditorSection
+ */
+NmtEditorSection *
+nmt_editor_section_new(const char *title, NmtNewtWidget *header_widget, gboolean show_by_default)
+{
+    return g_object_new(NMT_TYPE_EDITOR_SECTION,
+                        "title",
+                        title,
+                        "header-widget",
+                        header_widget,
+                        "show-by-default",
+                        show_by_default,
+                        NULL);
+}
+
+static void
+rebuild_header(NmtEditorSection *section)
+{
+    NmtEditorSectionPrivate *priv = NMT_EDITOR_SECTION_GET_PRIVATE(section);
+
+    /* Removing any widget in an NmtEditorGrid removes its whole row, so we can
+     * remove the existing title/widget/toggle by asking to remove toggle.
+     */
+    nmt_newt_container_remove(NMT_NEWT_CONTAINER(priv->header), priv->toggle);
+
+    nmt_editor_grid_append(priv->header, priv->title, priv->header_widget, priv->toggle);
+    nmt_editor_grid_set_row_flags(priv->header,
+                                  priv->toggle,
+                                  NMT_EDITOR_GRID_ROW_LABEL_ALIGN_LEFT
+                                      | NMT_EDITOR_GRID_ROW_EXTRA_ALIGN_RIGHT);
+}
+
+static void
+nmt_editor_section_init(NmtEditorSection *section)
+{
+    NmtEditorSectionPrivate *priv = NMT_EDITOR_SECTION_GET_PRIVATE(section);
+
+    priv->header = NMT_EDITOR_GRID(nmt_editor_grid_new());
+    priv->body   = NMT_EDITOR_GRID(nmt_editor_grid_new());
+    priv->toggle = nmt_newt_toggle_button_new(_("Hide"), _("Show"));
+    g_object_ref_sink(priv->toggle);
+
+    nmt_newt_section_set_header(NMT_NEWT_SECTION(section), NMT_NEWT_WIDGET(priv->header));
+    nmt_newt_section_set_body(NMT_NEWT_SECTION(section), NMT_NEWT_WIDGET(priv->body));
+
+    g_object_bind_property(priv->toggle, "active", section, "open", G_BINDING_SYNC_CREATE);
+}
+
+static void
+nmt_editor_section_finalize(GObject *object)
+{
+    NmtEditorSectionPrivate *priv = NMT_EDITOR_SECTION_GET_PRIVATE(object);
+
+    g_free(priv->title);
+    g_clear_object(&priv->header_widget);
+    g_clear_object(&priv->toggle);
+
+    G_OBJECT_CLASS(nmt_editor_section_parent_class)->finalize(object);
+}
+
+/**
+ * nmt_editor_section_get_header_widget:
+ * @section: the #NmtEditorSection
+ *
+ * Gets the section's header widget.
+ *
+ * Returns: the section's header widget.
+ */
+NmtNewtWidget *
+nmt_editor_section_get_header_widget(NmtEditorSection *section)
+{
+    NmtEditorSectionPrivate *priv = NMT_EDITOR_SECTION_GET_PRIVATE(section);
+
+    return priv->header_widget;
+}
+
+/**
+ * nmt_editor_section_get_body:
+ * @section: the #NmtEditorSection
+ *
+ * Gets the section's body grid, so that you can add things to it.
+ *
+ * Returns: the #NmtEditorGrid used for the section body
+ */
+NmtEditorGrid *
+nmt_editor_section_get_body(NmtEditorSection *section)
+{
+    NmtEditorSectionPrivate *priv = NMT_EDITOR_SECTION_GET_PRIVATE(section);
+
+    return priv->body;
+}
+
+/**
+ * nmt_editor_section_get_title:
+ * @section: the #NmtEditorSection
+ *
+ * Gets the section's title.
+ *
+ * Returns: the section's title
+ */
+const char *
+nmt_editor_section_get_title(NmtEditorSection *section)
+{
+    NmtEditorSectionPrivate *priv = NMT_EDITOR_SECTION_GET_PRIVATE(section);
+
+    return priv->title;
+}
+
+static void
+nmt_editor_section_set_property(GObject *     object,
+                                guint         prop_id,
+                                const GValue *value,
+                                GParamSpec *  pspec)
+{
+    NmtEditorSection *       section = NMT_EDITOR_SECTION(object);
+    NmtEditorSectionPrivate *priv    = NMT_EDITOR_SECTION_GET_PRIVATE(section);
+
+    switch (prop_id) {
+    case PROP_TITLE:
+        priv->title = g_value_dup_string(value);
+        rebuild_header(section);
+        break;
+    case PROP_SHOW_BY_DEFAULT:
+        priv->show_by_default = g_value_get_boolean(value);
+        nmt_newt_toggle_button_set_active(NMT_NEWT_TOGGLE_BUTTON(priv->toggle),
+                                          priv->show_by_default);
+        break;
+    case PROP_HEADER_WIDGET:
+        priv->header_widget = g_value_get_object(value);
+        if (priv->header_widget)
+            g_object_ref_sink(priv->header_widget);
+        rebuild_header(section);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nmt_editor_section_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+    NmtEditorSectionPrivate *priv = NMT_EDITOR_SECTION_GET_PRIVATE(object);
+
+    switch (prop_id) {
+    case PROP_TITLE:
+        g_value_set_string(value, priv->title);
+        break;
+    case PROP_SHOW_BY_DEFAULT:
+        g_value_set_boolean(value, priv->show_by_default);
+        break;
+    case PROP_HEADER_WIDGET:
+        g_value_set_object(value, priv->header_widget);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nmt_editor_section_class_init(NmtEditorSectionClass *section_class)
+{
+    GObjectClass *object_class = G_OBJECT_CLASS(section_class);
+
+    g_type_class_add_private(section_class, sizeof(NmtEditorSectionPrivate));
+
+    /* virtual methods */
+    object_class->set_property = nmt_editor_section_set_property;
+    object_class->get_property = nmt_editor_section_get_property;
+    object_class->finalize     = nmt_editor_section_finalize;
+
+    /* properties */
+
+    /**
+     * NmtEditorSection:title:
+     *
+     * The section's title.
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_TITLE,
+        g_param_spec_string("title",
+                            "",
+                            "",
+                            NULL,
+                            G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+    /**
+     * NmtEditorSection:show-by-default:
+     *
+     * Whether the section should be expanded by default.
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_SHOW_BY_DEFAULT,
+        g_param_spec_boolean("show-by-default",
+                             "",
+                             "",
+                             TRUE,
+                             G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+    /**
+     * NmtEditorSection:header-widget:
+     *
+     * The widget (if any) that appears between the section title and its toggle
+     * button.
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_HEADER_WIDGET,
+        g_param_spec_object("header-widget",
+                            "",
+                            "",
+                            NMT_TYPE_NEWT_WIDGET,
+                            G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+}
diff --git a/src/nmtui/nmt-editor-section.h b/src/nmtui/nmt-editor-section.h
new file mode 100644
index 00000000..b7baf821
--- /dev/null
+++ b/src/nmtui/nmt-editor-section.h
@@ -0,0 +1,42 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+#ifndef NMT_EDITOR_SECTION_H
+#define NMT_EDITOR_SECTION_H
+
+#include "libnmt-newt/nmt-newt-section.h"
+#include "nmt-editor-grid.h"
+
+#define NMT_TYPE_EDITOR_SECTION (nmt_editor_section_get_type())
+#define NMT_EDITOR_SECTION(obj) \
+    (G_TYPE_CHECK_INSTANCE_CAST((obj), NMT_TYPE_EDITOR_SECTION, NmtEditorSection))
+#define NMT_EDITOR_SECTION_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NMT_TYPE_EDITOR_SECTION, NmtEditorSectionClass))
+#define NMT_IS_EDITOR_SECTION(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMT_TYPE_EDITOR_SECTION))
+#define NMT_IS_EDITOR_SECTION_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_TYPE((klass), NMT_TYPE_EDITOR_SECTION))
+#define NMT_EDITOR_SECTION_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NMT_TYPE_EDITOR_SECTION, NmtEditorSectionClass))
+
+typedef struct {
+    NmtNewtSection parent;
+
+} NmtEditorSection;
+
+typedef struct {
+    NmtNewtSectionClass parent;
+
+} NmtEditorSectionClass;
+
+GType nmt_editor_section_get_type(void);
+
+NmtEditorSection *
+nmt_editor_section_new(const char *title, NmtNewtWidget *header_widget, gboolean show_by_default);
+
+const char *   nmt_editor_section_get_title(NmtEditorSection *section);
+NmtNewtWidget *nmt_editor_section_get_header_widget(NmtEditorSection *section);
+NmtEditorGrid *nmt_editor_section_get_body(NmtEditorSection *section);
+
+#endif /* NMT_EDITOR_SECTION_H */
diff --git a/src/nmtui/nmt-editor.c b/src/nmtui/nmt-editor.c
new file mode 100644
index 00000000..0ef23693
--- /dev/null
+++ b/src/nmtui/nmt-editor.c
@@ -0,0 +1,534 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+/**
+ * SECTION:nmt-editor
+ * @short_description: Connection editing form
+ *
+ * #NmtEditor is the top-level form for editing a connection.
+ */
+
+#include "libnm-client-aux-extern/nm-default-client.h"
+
+#include "nmt-editor.h"
+
+#include "nm-utils.h"
+
+#include "nmtui.h"
+
+#include "nm-editor-utils.h"
+#include "nmt-utils.h"
+
+#include "nmt-device-entry.h"
+#include "nmt-mac-entry.h"
+#include "nmt-mtu-entry.h"
+
+#include "nmt-page-bond.h"
+#include "nmt-page-bridge.h"
+#include "nmt-page-bridge-port.h"
+#include "nmt-page-dsl.h"
+#include "nmt-page-ethernet.h"
+#include "nmt-page-infiniband.h"
+#include "nmt-page-ip-tunnel.h"
+#include "nmt-page-ip4.h"
+#include "nmt-page-ip6.h"
+#include "nmt-page-ppp.h"
+#include "nmt-page-team.h"
+#include "nmt-page-team-port.h"
+#include "nmt-page-vlan.h"
+#include "nmt-page-wifi.h"
+
+#include "libnmc-setting/nm-meta-setting-access.h"
+
+G_DEFINE_TYPE(NmtEditor, nmt_editor, NMT_TYPE_NEWT_FORM)
+
+#define NMT_EDITOR_GET_PRIVATE(o) \
+    (G_TYPE_INSTANCE_GET_PRIVATE((o), NMT_TYPE_EDITOR, NmtEditorPrivate))
+
+typedef struct {
+    NMConnection *orig_connection;
+    NMConnection *edit_connection;
+
+    NMEditorConnectionTypeData *type_data;
+
+    GSList *       pages;
+    NmtNewtWidget *ok, *cancel;
+    gboolean       running;
+} NmtEditorPrivate;
+
+enum {
+    PROP_0,
+    PROP_CONNECTION,
+    PROP_TYPE_DATA,
+
+    LAST_PROP
+};
+
+/**
+ * nmt_editor_new:
+ * @connection: the #NMConnection to edit
+ *
+ * Creates a new #NmtEditor to edit @connection.
+ *
+ * Returns: a new #NmtEditor
+ */
+NmtNewtForm *
+nmt_editor_new(NMConnection *connection)
+{
+    NMEditorConnectionTypeData *type_data;
+
+    type_data = nm_editor_utils_get_connection_type_data(connection);
+    if (!type_data) {
+        NMSettingConnection *s_con;
+
+        s_con = nm_connection_get_setting_connection(connection);
+        if (s_con) {
+            nmt_newt_message_dialog(_("Could not create editor for connection '%s' of type '%s'."),
+                                    nm_connection_get_id(connection),
+                                    nm_setting_connection_get_connection_type(s_con));
+        } else {
+            nmt_newt_message_dialog(_("Could not create editor for invalid connection '%s'."),
+                                    nm_connection_get_id(connection));
+        }
+
+        return NULL;
+    }
+
+    return g_object_new(NMT_TYPE_EDITOR,
+                        "connection",
+                        connection,
+                        "type-data",
+                        type_data,
+                        "title",
+                        _("Edit Connection"),
+                        "fullscreen-vertical",
+                        TRUE,
+                        NULL);
+}
+
+static void
+nmt_editor_init(NmtEditor *entry)
+{}
+
+static void
+connection_updated(GObject *connection, GAsyncResult *result, gpointer op)
+{
+    GError *error = NULL;
+
+    nm_remote_connection_commit_changes_finish(NM_REMOTE_CONNECTION(connection), result, &error);
+    nmt_sync_op_complete_boolean(op, error == NULL, error);
+    g_clear_error(&error);
+}
+
+static void
+connection_added(GObject *client, GAsyncResult *result, gpointer op)
+{
+    NMRemoteConnection *connection;
+    GError *            error = NULL;
+
+    connection = nm_client_add_connection_finish(NM_CLIENT(client), result, &error);
+    nmt_sync_op_complete_boolean(op, error == NULL, error);
+    g_clear_object(&connection);
+    g_clear_error(&error);
+}
+
+static void
+page_saved(gpointer data, gpointer user_data)
+{
+    NmtEditorPage *page = data;
+
+    nmt_editor_page_saved(page);
+}
+
+static void
+save_connection_and_exit(NmtNewtButton *button, gpointer user_data)
+{
+    NmtEditor *       editor = user_data;
+    NmtEditorPrivate *priv   = NMT_EDITOR_GET_PRIVATE(editor);
+    NmtSyncOp         op;
+    GError *          error = NULL;
+
+    nm_connection_replace_settings_from_connection(priv->orig_connection, priv->edit_connection);
+
+    nmt_sync_op_init(&op);
+    if (NM_IS_REMOTE_CONNECTION(priv->orig_connection)) {
+        nm_remote_connection_commit_changes_async(NM_REMOTE_CONNECTION(priv->orig_connection),
+                                                  TRUE,
+                                                  NULL,
+                                                  connection_updated,
+                                                  &op);
+        if (!nmt_sync_op_wait_boolean(&op, &error)) {
+            nmt_newt_message_dialog(_("Unable to save connection: %s"), error->message);
+            g_error_free(error);
+            return;
+        }
+
+        /* Clear secrets so they don't lay around in memory; they'll get
+         * requested again anyway next time the connection is edited.
+         */
+        nm_connection_clear_secrets(priv->orig_connection);
+    } else {
+        nm_client_add_connection_async(nm_client,
+                                       priv->orig_connection,
+                                       TRUE,
+                                       NULL,
+                                       connection_added,
+                                       &op);
+        if (!nmt_sync_op_wait_boolean(&op, &error)) {
+            nmt_newt_message_dialog(_("Unable to add new connection: %s"), error->message);
+            g_error_free(error);
+            return;
+        }
+    }
+
+    /* Let the page know that it was saved. */
+    g_slist_foreach(priv->pages, page_saved, NULL);
+
+    nmt_newt_form_quit(NMT_NEWT_FORM(editor));
+}
+
+static void
+got_secrets(GObject *object, GAsyncResult *result, gpointer op)
+{
+    GVariant *secrets;
+    GError *  error = NULL;
+
+    secrets = nm_remote_connection_get_secrets_finish(NM_REMOTE_CONNECTION(object), result, &error);
+    if (secrets)
+        g_variant_ref(secrets);
+    nmt_sync_op_complete_pointer(op, secrets, error);
+    g_clear_error(&error);
+}
+
+static NMConnection *
+build_edit_connection(NMConnection *orig_connection)
+{
+    NMConnection *edit_connection;
+    GVariant *    settings, *secrets;
+    GVariantIter  iter;
+    const char *  setting_name;
+    NmtSyncOp     op;
+
+    edit_connection = nm_simple_connection_new_clone(orig_connection);
+
+    if (!NM_IS_REMOTE_CONNECTION(orig_connection))
+        return edit_connection;
+
+    settings = nm_connection_to_dbus(orig_connection, NM_CONNECTION_SERIALIZE_WITH_NON_SECRET);
+    g_variant_iter_init(&iter, settings);
+    while (g_variant_iter_next(&iter, "{&s@a{sv}}", &setting_name, NULL)) {
+        if (!nm_meta_setting_info_editor_has_secrets(
+                nm_meta_setting_info_editor_find_by_name(setting_name, FALSE)))
+            continue;
+        nmt_sync_op_init(&op);
+        nm_remote_connection_get_secrets_async(NM_REMOTE_CONNECTION(orig_connection),
+                                               setting_name,
+                                               NULL,
+                                               got_secrets,
+                                               &op);
+        /* FIXME: error handling */
+        secrets = nmt_sync_op_wait_pointer(&op, NULL);
+        if (secrets) {
+            (void) nm_connection_update_secrets(edit_connection, setting_name, secrets, NULL);
+            g_variant_unref(secrets);
+        }
+    }
+    g_variant_unref(settings);
+
+    return edit_connection;
+}
+
+static gboolean
+permissions_transform_to_allusers(GBinding *    binding,
+                                  const GValue *source_value,
+                                  GValue *      target_value,
+                                  gpointer      user_data)
+{
+    char **perms = g_value_get_boxed(source_value);
+
+    g_value_set_boolean(target_value, g_strv_length(perms) == 0);
+    return TRUE;
+}
+
+static gboolean
+permissions_transform_from_allusers(GBinding *    binding,
+                                    const GValue *source_value,
+                                    GValue *      target_value,
+                                    gpointer      user_data)
+{
+    gboolean allusers = g_value_get_boolean(source_value);
+    char **  perms    = NULL;
+
+    if (!allusers) {
+        perms = g_new(char *, 2);
+
+        perms[0] = g_strdup_printf("user:%s:", g_get_user_name());
+        perms[1] = NULL;
+    }
+    g_value_take_boxed(target_value, perms);
+    return TRUE;
+}
+
+static NmtNewtWidget *
+add_sections_for_page(NmtEditor *editor, NmtEditorGrid *grid, NmtEditorPage *page)
+{
+    NmtEditorPrivate *priv          = NMT_EDITOR_GET_PRIVATE(editor);
+    NmtNewtWidget *   first_section = NULL;
+    const GSList *    sections, *iter;
+
+    g_return_val_if_fail(NMT_IS_EDITOR_PAGE(page), NULL);
+
+    priv->pages = g_slist_prepend(priv->pages, page);
+
+    sections = nmt_editor_page_get_sections(page);
+    for (iter = sections; iter; iter = iter->next) {
+        if (!first_section)
+            first_section = iter->data;
+        nmt_editor_grid_append(grid, NULL, iter->data, NULL);
+    }
+
+    return first_section;
+}
+
+static void
+nmt_editor_constructed(GObject *object)
+{
+    NmtEditor *          editor = NMT_EDITOR(object);
+    NmtEditorPrivate *   priv   = NMT_EDITOR_GET_PRIVATE(editor);
+    NMSettingConnection *s_con;
+    NmtNewtWidget *      vbox, *widget, *buttons;
+    NmtEditorGrid *      grid;
+    const char *         deventry_label;
+    NmtDeviceEntry *     deventry;
+    GType                hardware_type;
+    const char *         slave_type;
+    NmtEditorPage *      page;
+
+    if (G_OBJECT_CLASS(nmt_editor_parent_class)->constructed)
+        G_OBJECT_CLASS(nmt_editor_parent_class)->constructed(object);
+
+    priv->edit_connection = build_edit_connection(priv->orig_connection);
+
+    vbox = nmt_newt_grid_new();
+
+    s_con = nm_connection_get_setting_connection(priv->edit_connection);
+
+    grid = NMT_EDITOR_GRID(nmt_editor_grid_new());
+    nmt_newt_grid_add(NMT_NEWT_GRID(vbox), NMT_NEWT_WIDGET(grid), 0, 0);
+
+    /* Add the top widgets */
+
+    widget = nmt_newt_entry_new(40, NMT_NEWT_ENTRY_NONEMPTY);
+    g_object_bind_property(s_con,
+                           NM_SETTING_CONNECTION_ID,
+                           widget,
+                           "text",
+                           G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+    nmt_editor_grid_append(grid, _("Profile name"), widget, NULL);
+
+    if (priv->type_data->virtual)
+        hardware_type = G_TYPE_NONE;
+    else
+        hardware_type = priv->type_data->device_type;
+
+    /* For connections involving multiple network devices, clarify which one
+     * NMSettingConnection:interface-name refers to.
+     */
+    if (nm_connection_is_type(priv->edit_connection, NM_SETTING_PPPOE_SETTING_NAME))
+        deventry_label = _("Ethernet device");
+    else
+        deventry_label = _("Device");
+
+    widget = nmt_device_entry_new(deventry_label, 40, hardware_type);
+    nmt_editor_grid_append(grid, NULL, widget, NULL);
+    deventry = NMT_DEVICE_ENTRY(widget);
+    g_object_bind_property(s_con,
+                           NM_SETTING_CONNECTION_INTERFACE_NAME,
+                           deventry,
+                           "interface-name",
+                           G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+
+    nmt_editor_grid_append(grid, NULL, nmt_newt_separator_new(), NULL);
+
+    /* Now add the various pages... */
+
+    if (nm_connection_is_type(priv->edit_connection, NM_SETTING_BOND_SETTING_NAME))
+        page = nmt_page_bond_new(priv->edit_connection, deventry);
+    else if (nm_connection_is_type(priv->edit_connection, NM_SETTING_BRIDGE_SETTING_NAME))
+        page = nmt_page_bridge_new(priv->edit_connection, deventry);
+    else if (nm_connection_is_type(priv->edit_connection, NM_SETTING_INFINIBAND_SETTING_NAME))
+        page = nmt_page_infiniband_new(priv->edit_connection, deventry);
+    else if (nm_connection_is_type(priv->edit_connection, NM_SETTING_PPPOE_SETTING_NAME))
+        page = nmt_page_dsl_new(priv->edit_connection, deventry);
+    else if (nm_connection_is_type(priv->edit_connection, NM_SETTING_TEAM_SETTING_NAME))
+        page = nmt_page_team_new(priv->edit_connection, deventry);
+    else if (nm_connection_is_type(priv->edit_connection, NM_SETTING_VLAN_SETTING_NAME))
+        page = nmt_page_vlan_new(priv->edit_connection, deventry);
+    else if (nm_connection_is_type(priv->edit_connection, NM_SETTING_WIRED_SETTING_NAME))
+        page = nmt_page_ethernet_new(priv->edit_connection, deventry);
+    else if (nm_connection_is_type(priv->edit_connection, NM_SETTING_WIRELESS_SETTING_NAME))
+        page = nmt_page_wifi_new(priv->edit_connection, deventry);
+    else if (nm_connection_is_type(priv->edit_connection, NM_SETTING_IP_TUNNEL_SETTING_NAME))
+        page = nmt_page_ip_tunnel_new(priv->edit_connection, deventry);
+    else
+        g_assert_not_reached();
+
+    add_sections_for_page(editor, grid, page);
+    nmt_editor_grid_append(grid, NULL, nmt_newt_separator_new(), NULL);
+
+    slave_type = nm_setting_connection_get_slave_type(s_con);
+    if (slave_type) {
+        if (!strcmp(slave_type, NM_SETTING_BRIDGE_SETTING_NAME))
+            add_sections_for_page(editor, grid, nmt_page_bridge_port_new(priv->edit_connection));
+        else if (!strcmp(slave_type, NM_SETTING_TEAM_SETTING_NAME))
+            add_sections_for_page(editor, grid, nmt_page_team_port_new(priv->edit_connection));
+    } else {
+        NmtNewtWidget *section;
+
+        section = add_sections_for_page(editor, grid, nmt_page_ip4_new(priv->edit_connection));
+
+        /* Add a separator between ip4 and ip6 that's only visible if ip4 is open */
+        widget = nmt_newt_separator_new();
+        g_object_bind_property(section, "open", widget, "visible", G_BINDING_SYNC_CREATE);
+        nmt_editor_grid_append(grid, NULL, widget, NULL);
+
+        add_sections_for_page(editor, grid, nmt_page_ip6_new(priv->edit_connection));
+
+        nmt_editor_grid_append(grid, NULL, nmt_newt_separator_new(), NULL);
+    }
+
+    /* And finally the bottom widgets */
+
+    widget = nmt_newt_checkbox_new(_("Automatically connect"));
+    g_object_bind_property(s_con,
+                           NM_SETTING_CONNECTION_AUTOCONNECT,
+                           widget,
+                           "active",
+                           G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+    nmt_editor_grid_append(grid, NULL, widget, NULL);
+
+    widget = nmt_newt_checkbox_new(_("Available to all users"));
+    g_object_bind_property_full(s_con,
+                                NM_SETTING_CONNECTION_PERMISSIONS,
+                                widget,
+                                "active",
+                                G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE,
+                                permissions_transform_to_allusers,
+                                permissions_transform_from_allusers,
+                                NULL,
+                                NULL);
+    nmt_editor_grid_append(grid, NULL, widget, NULL);
+
+    /* And the button box */
+
+    buttons = nmt_newt_button_box_new(NMT_NEWT_BUTTON_BOX_HORIZONTAL);
+    nmt_newt_grid_add(NMT_NEWT_GRID(vbox), buttons, 0, 1);
+    nmt_newt_widget_set_padding(buttons, 0, 1, 0, 0);
+
+    priv->cancel = nmt_newt_button_box_add_end(NMT_NEWT_BUTTON_BOX(buttons), _("Cancel"));
+    nmt_newt_widget_set_exit_on_activate(priv->cancel, TRUE);
+
+    priv->ok = nmt_newt_button_box_add_end(NMT_NEWT_BUTTON_BOX(buttons), _("OK"));
+    g_signal_connect(priv->ok, "clicked", G_CALLBACK(save_connection_and_exit), editor);
+    g_object_bind_property(NMT_NEWT_WIDGET(grid),
+                           "valid",
+                           priv->ok,
+                           "sensitive",
+                           G_BINDING_SYNC_CREATE);
+
+    nmt_newt_form_set_content(NMT_NEWT_FORM(editor), vbox);
+}
+
+static void
+nmt_editor_finalize(GObject *object)
+{
+    NmtEditorPrivate *priv = NMT_EDITOR_GET_PRIVATE(object);
+
+    g_clear_object(&priv->orig_connection);
+    g_clear_object(&priv->edit_connection);
+
+    g_slist_free_full(priv->pages, g_object_unref);
+
+    g_clear_object(&priv->ok);
+    g_clear_object(&priv->cancel);
+
+    G_OBJECT_CLASS(nmt_editor_parent_class)->finalize(object);
+}
+
+static void
+nmt_editor_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+    NmtEditorPrivate *priv = NMT_EDITOR_GET_PRIVATE(object);
+
+    switch (prop_id) {
+    case PROP_CONNECTION:
+        priv->orig_connection = g_value_dup_object(value);
+        break;
+    case PROP_TYPE_DATA:
+        priv->type_data = g_value_get_pointer(value);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nmt_editor_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+    NmtEditorPrivate *priv = NMT_EDITOR_GET_PRIVATE(object);
+
+    switch (prop_id) {
+    case PROP_CONNECTION:
+        g_value_set_object(value, priv->orig_connection);
+        break;
+    case PROP_TYPE_DATA:
+        g_value_set_pointer(value, priv->type_data);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nmt_editor_class_init(NmtEditorClass *entry_class)
+{
+    GObjectClass *object_class = G_OBJECT_CLASS(entry_class);
+
+    g_type_class_add_private(entry_class, sizeof(NmtEditorPrivate));
+
+    /* virtual methods */
+    object_class->constructed  = nmt_editor_constructed;
+    object_class->set_property = nmt_editor_set_property;
+    object_class->get_property = nmt_editor_get_property;
+    object_class->finalize     = nmt_editor_finalize;
+
+    /**
+     * NmtEditor:connection:
+     *
+     * The connection being edited.
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_CONNECTION,
+        g_param_spec_object("connection",
+                            "",
+                            "",
+                            NM_TYPE_CONNECTION,
+                            G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+    /**
+     * NmtEditor:type-data:
+     *
+     * The #NmEditorConnectionTypeData for #NmtEditor:connection.
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_TYPE_DATA,
+        g_param_spec_pointer("type-data",
+                             "",
+                             "",
+                             G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+}
diff --git a/src/nmtui/nmt-editor.h b/src/nmtui/nmt-editor.h
new file mode 100644
index 00000000..49fe89d3
--- /dev/null
+++ b/src/nmtui/nmt-editor.h
@@ -0,0 +1,33 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+#ifndef NMT_EDITOR_H
+#define NMT_EDITOR_H
+
+#include "libnmt-newt/nmt-newt.h"
+
+#define NMT_TYPE_EDITOR            (nmt_editor_get_type())
+#define NMT_EDITOR(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), NMT_TYPE_EDITOR, NmtEditor))
+#define NMT_EDITOR_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), NMT_TYPE_EDITOR, NmtEditorClass))
+#define NMT_IS_EDITOR(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMT_TYPE_EDITOR))
+#define NMT_IS_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NMT_TYPE_EDITOR))
+#define NMT_EDITOR_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NMT_TYPE_EDITOR, NmtEditorClass))
+
+typedef struct {
+    NmtNewtForm parent;
+
+} NmtEditor;
+
+typedef struct {
+    NmtNewtFormClass parent;
+
+} NmtEditorClass;
+
+GType nmt_editor_get_type(void);
+
+NmtNewtForm *nmt_editor_new(NMConnection *connection);
+
+#endif /* NMT_EDITOR_H */
diff --git a/src/nmtui/nmt-ip-entry.c b/src/nmtui/nmt-ip-entry.c
new file mode 100644
index 00000000..b801eec4
--- /dev/null
+++ b/src/nmtui/nmt-ip-entry.c
@@ -0,0 +1,221 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+/**
+ * SECTION:nmt-ip-entry
+ * @short_description: #NmtNewtEntry for IP address entry
+ *
+ * #NmtIPEntry is an #NmtNewtEntry for entering IP addresses, or IP
+ * address/prefix combination. It will only allow typing characters
+ * that are valid in an IP address, and will set its
+ * #NmtNewtWidget:valid property depending on whether it currently
+ * contains a valid IP address.
+ */
+
+#include "libnm-client-aux-extern/nm-default-client.h"
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <stdlib.h>
+
+#include "nmt-ip-entry.h"
+
+G_DEFINE_TYPE(NmtIPEntry, nmt_ip_entry, NMT_TYPE_NEWT_ENTRY)
+
+#define NMT_IP_ENTRY_GET_PRIVATE(o) \
+    (G_TYPE_INSTANCE_GET_PRIVATE((o), NMT_TYPE_IP_ENTRY, NmtIPEntryPrivate))
+
+typedef struct {
+    int      family;
+    gboolean prefix;
+    gboolean optional;
+
+} NmtIPEntryPrivate;
+
+enum {
+    PROP_0,
+    PROP_FAMILY,
+    PROP_PREFIX,
+    PROP_OPTIONAL,
+
+    LAST_PROP
+};
+
+/**
+ * nmt_ip_entry_new:
+ * @width: the width of the entry
+ * @family: the IP address family. Eg, %AF_INET
+ * @prefix: whether to require a trailing "/prefix"
+ * @optional: whether the address is optional
+ *
+ * Creates a new #NmtIPEntry, to accept IP addresses in the indicated
+ * @family, or (if @prefix is %TRUE), to accept IP address/prefix combos.
+ *
+ * If @optional is %TRUE then the address is considered optional, and
+ * so will still be #NmtNewtWidget:valid even when it is empty. If
+ * @optional is %FALSE, the entry will be invalid when it is empty.
+ */
+NmtNewtWidget *
+nmt_ip_entry_new(int width, int family, gboolean prefix, gboolean optional)
+{
+    return g_object_new(NMT_TYPE_IP_ENTRY,
+                        "width",
+                        width,
+                        "family",
+                        family,
+                        "prefix",
+                        prefix,
+                        "optional",
+                        optional,
+                        NULL);
+}
+
+static gboolean
+ip_entry_filter(NmtNewtEntry *entry, const char *text, int ch, int position, gpointer user_data)
+{
+    NmtIPEntryPrivate *priv = NMT_IP_ENTRY_GET_PRIVATE(entry);
+    const char *       slash;
+    gboolean           inaddr;
+
+    if (g_ascii_isdigit(ch))
+        return TRUE;
+
+    slash = strchr(text, '/');
+    if (ch == '/')
+        return priv->prefix && slash == NULL;
+
+    inaddr = !slash || (position <= (slash - text));
+
+    if (priv->family == AF_INET) {
+        if (ch == '.')
+            return inaddr;
+        else
+            return FALSE;
+    } else if (priv->family == AF_INET6) {
+        if (g_ascii_isxdigit(ch) || ch == ':')
+            return inaddr;
+        else
+            return FALSE;
+    } else
+        g_return_val_if_reached(FALSE);
+}
+
+static gboolean
+ip_entry_validate(NmtNewtEntry *entry, const char *text, gpointer user_data)
+{
+    NmtIPEntryPrivate *priv = NMT_IP_ENTRY_GET_PRIVATE(entry);
+
+    if (!*text)
+        return priv->optional;
+    if (priv->prefix)
+        return nm_utils_parse_inaddr_prefix(priv->family, text, NULL, NULL);
+    return nm_utils_parse_inaddr(priv->family, text, NULL);
+}
+
+static void
+nmt_ip_entry_init(NmtIPEntry *entry)
+{
+    nmt_newt_entry_set_filter(NMT_NEWT_ENTRY(entry), ip_entry_filter, NULL);
+    nmt_newt_entry_set_validator(NMT_NEWT_ENTRY(entry), ip_entry_validate, NULL);
+}
+
+static void
+nmt_ip_entry_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+    NmtIPEntryPrivate *priv = NMT_IP_ENTRY_GET_PRIVATE(object);
+
+    switch (prop_id) {
+    case PROP_FAMILY:
+        priv->family = g_value_get_int(value);
+        break;
+    case PROP_PREFIX:
+        priv->prefix = g_value_get_boolean(value);
+        break;
+    case PROP_OPTIONAL:
+        priv->optional = g_value_get_boolean(value);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nmt_ip_entry_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+    NmtIPEntryPrivate *priv = NMT_IP_ENTRY_GET_PRIVATE(object);
+
+    switch (prop_id) {
+    case PROP_FAMILY:
+        g_value_set_int(value, priv->family);
+        break;
+    case PROP_PREFIX:
+        g_value_set_boolean(value, priv->prefix);
+        break;
+    case PROP_OPTIONAL:
+        g_value_set_boolean(value, priv->optional);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nmt_ip_entry_class_init(NmtIPEntryClass *entry_class)
+{
+    GObjectClass *object_class = G_OBJECT_CLASS(entry_class);
+
+    g_type_class_add_private(entry_class, sizeof(NmtIPEntryPrivate));
+
+    /* virtual methods */
+    object_class->set_property = nmt_ip_entry_set_property;
+    object_class->get_property = nmt_ip_entry_get_property;
+
+    /**
+     * NmtIPEntry:family:
+     *
+     * The address family. Eg, %AF_INET
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_FAMILY,
+        g_param_spec_int("family",
+                         "",
+                         "",
+                         0,
+                         G_MAXINT,
+                         0,
+                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+    /**
+     * NmtIPEntry:prefix:
+     *
+     * If %TRUE, the entry accepts address/prefix combinations. If
+     * %FALSE it accepts just addresses.
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_PREFIX,
+        g_param_spec_boolean("prefix",
+                             "",
+                             "",
+                             FALSE,
+                             G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+    /**
+     * NmtIPEntry:optional:
+     *
+     * If %TRUE, the entry will be #NmtNewtWidget:valid when it is
+     * empty. If %FALSE, it will only be valid when it contains a
+     * valid address or address/prefix.
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_OPTIONAL,
+        g_param_spec_boolean("optional",
+                             "",
+                             "",
+                             FALSE,
+                             G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+}
diff --git a/src/nmtui/nmt-ip-entry.h b/src/nmtui/nmt-ip-entry.h
new file mode 100644
index 00000000..1489326d
--- /dev/null
+++ b/src/nmtui/nmt-ip-entry.h
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+#ifndef NMT_IP_ENTRY_H
+#define NMT_IP_ENTRY_H
+
+#include "libnmt-newt/nmt-newt.h"
+
+#define NMT_TYPE_IP_ENTRY (nmt_ip_entry_get_type())
+#define NMT_IP_ENTRY(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NMT_TYPE_IP_ENTRY, NmtIPEntry))
+#define NMT_IP_ENTRY_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NMT_TYPE_IP_ENTRY, NmtIPEntryClass))
+#define NMT_IS_IP_ENTRY(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMT_TYPE_IP_ENTRY))
+#define NMT_IS_IP_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NMT_TYPE_IP_ENTRY))
+#define NMT_IP_ENTRY_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NMT_TYPE_IP_ENTRY, NmtIPEntryClass))
+
+typedef struct {
+    NmtNewtEntry parent;
+
+} NmtIPEntry;
+
+typedef struct {
+    NmtNewtEntryClass parent;
+
+} NmtIPEntryClass;
+
+GType nmt_ip_entry_get_type(void);
+
+NmtNewtWidget *nmt_ip_entry_new(int width, int family, gboolean prefix, gboolean optional);
+
+#endif /* NMT_IP_ENTRY_H */
diff --git a/src/nmtui/nmt-mac-entry.c b/src/nmtui/nmt-mac-entry.c
new file mode 100644
index 00000000..3f71f4c9
--- /dev/null
+++ b/src/nmtui/nmt-mac-entry.c
@@ -0,0 +1,243 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+/**
+ * SECTION:nmt-mac-entry
+ * @short_description: #NmtNewtEntry for hardware address entry
+ *
+ * #NmtMacEntry is an #NmtNewtEntry for entering hardware addresses.
+ * It will only allow typing characters that are valid in a hardware
+ * address, and will set its #NmtNewtWidget:valid property depending
+ * on whether it currently contains a valid hardware address.
+ */
+
+#include "libnm-client-aux-extern/nm-default-client.h"
+
+#include "nmt-mac-entry.h"
+
+#include <linux/if_ether.h>
+#include <linux/if_infiniband.h>
+
+#include "libnm-core-aux-intern/nm-common-macros.h"
+
+G_DEFINE_TYPE(NmtMacEntry, nmt_mac_entry, NMT_TYPE_NEWT_ENTRY)
+
+#define NMT_MAC_ENTRY_GET_PRIVATE(o) \
+    (G_TYPE_INSTANCE_GET_PRIVATE((o), NMT_TYPE_MAC_ENTRY, NmtMacEntryPrivate))
+
+typedef struct {
+    int             mac_length;
+    int             mac_str_length;
+    NmtMacEntryType entry_type;
+
+} NmtMacEntryPrivate;
+
+enum {
+    PROP_0,
+    PROP_MAC_LENGTH,
+    PROP_MAC_ADDRESS,
+    PROP_ENTRY_TYPE,
+
+    LAST_PROP
+};
+
+/**
+ * nmt_mac_entry_new:
+ * @width: the width in characters of the entry
+ * @mac_length: the length in bytes of the hardware address
+ *   (either %ETH_ALEN or %INFINIBAND_ALEN)
+ * @entry_type: the type of the entry.
+ *
+ * Creates a new #NmtMacEntry.
+ *
+ * Returns: a new #NmtMacEntry.
+ */
+NmtNewtWidget *
+nmt_mac_entry_new(int width, int mac_length, NmtMacEntryType entry_type)
+{
+    return g_object_new(NMT_TYPE_MAC_ENTRY,
+                        "width",
+                        width,
+                        "mac-length",
+                        mac_length,
+                        "entry-type",
+                        (int) entry_type,
+                        NULL);
+}
+
+static gboolean
+mac_filter(NmtNewtEntry *entry, const char *text, int ch, int position, gpointer user_data)
+{
+    NmtMacEntryPrivate *priv = NMT_MAC_ENTRY_GET_PRIVATE(entry);
+
+    if (priv->entry_type != NMT_MAC_ENTRY_TYPE_MAC)
+        return TRUE;
+
+    if (position >= priv->mac_str_length)
+        return FALSE;
+
+    return g_ascii_isxdigit(ch) || ch == ':';
+}
+
+static gboolean
+mac_validator(NmtNewtEntry *entry, const char *text, gpointer user_data)
+{
+    NmtMacEntryPrivate *priv = NMT_MAC_ENTRY_GET_PRIVATE(entry);
+    const char *        p;
+
+    if (!*text)
+        return TRUE;
+
+    if (priv->entry_type == NMT_MAC_ENTRY_TYPE_CLONED) {
+        if (NM_CLONED_MAC_IS_SPECIAL(text))
+            return TRUE;
+    }
+
+    p = text;
+    while (g_ascii_isxdigit(p[0]) && g_ascii_isxdigit(p[1]) && p[2] == ':')
+        p += 3;
+
+    if (!g_ascii_isxdigit(p[0]) || !g_ascii_isxdigit(p[1]))
+        return FALSE;
+    p += 2;
+
+    if (!*p)
+        return (p - text == priv->mac_str_length);
+
+    if (g_ascii_isxdigit(p[0]) && !p[1] && p - text < priv->mac_str_length) {
+        char *fixed = g_strdup_printf("%.*s:%c", (int) (p - text), text, *p);
+
+        nmt_newt_entry_set_text(entry, fixed);
+        g_free(fixed);
+
+        /* FIXME: NmtNewtEntry doesn't correctly deal with us calling set_text()
+         * from inside the validator.
+         */
+        nmt_newt_widget_needs_rebuild(NMT_NEWT_WIDGET(entry));
+    }
+
+    return FALSE;
+}
+
+static void
+nmt_mac_entry_init(NmtMacEntry *entry)
+{
+    nmt_newt_entry_set_filter(NMT_NEWT_ENTRY(entry), mac_filter, NULL);
+    nmt_newt_entry_set_validator(NMT_NEWT_ENTRY(entry), mac_validator, NULL);
+}
+
+static void
+nmt_mac_entry_notify(GObject *object, GParamSpec *pspec)
+{
+    if (G_OBJECT_CLASS(nmt_mac_entry_parent_class)->notify)
+        G_OBJECT_CLASS(nmt_mac_entry_parent_class)->notify(object, pspec);
+
+    if (pspec->owner_type == NMT_TYPE_NEWT_ENTRY && !strcmp(pspec->name, "text"))
+        g_object_notify(object, "mac-address");
+}
+
+static void
+nmt_mac_entry_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+    NmtMacEntryPrivate *priv = NMT_MAC_ENTRY_GET_PRIVATE(object);
+
+    switch (prop_id) {
+    case PROP_MAC_LENGTH:
+        priv->mac_length     = g_value_get_int(value);
+        priv->mac_str_length = priv->mac_length * 3 - 1;
+        break;
+    case PROP_MAC_ADDRESS:
+        nmt_newt_entry_set_text(NMT_NEWT_ENTRY(object), g_value_get_string(value));
+        break;
+    case PROP_ENTRY_TYPE:
+        /* construct-only */
+        priv->entry_type = g_value_get_int(value);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nmt_mac_entry_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+    NmtMacEntryPrivate *priv = NMT_MAC_ENTRY_GET_PRIVATE(object);
+
+    switch (prop_id) {
+    case PROP_MAC_LENGTH:
+        g_value_set_int(value, priv->mac_length);
+        break;
+    case PROP_MAC_ADDRESS:
+        g_value_set_string(value,
+                           nm_str_not_empty(nmt_newt_entry_get_text(NMT_NEWT_ENTRY(object))));
+        break;
+    case PROP_ENTRY_TYPE:
+        g_value_set_int(value, priv->entry_type);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nmt_mac_entry_class_init(NmtMacEntryClass *entry_class)
+{
+    GObjectClass *object_class = G_OBJECT_CLASS(entry_class);
+
+    g_type_class_add_private(entry_class, sizeof(NmtMacEntryPrivate));
+
+    /* virtual methods */
+    object_class->notify       = nmt_mac_entry_notify;
+    object_class->set_property = nmt_mac_entry_set_property;
+    object_class->get_property = nmt_mac_entry_get_property;
+
+    /**
+     * NmtMacEntry:mac-length:
+     *
+     * The length in bytes of the hardware address type the entry
+     * accepts: either %ETH_ALEN or %INFINIBAND_ALEN.
+     */
+    g_object_class_install_property(object_class,
+                                    PROP_MAC_LENGTH,
+                                    g_param_spec_int("mac-length",
+                                                     "",
+                                                     "",
+                                                     0,
+                                                     INFINIBAND_ALEN,
+                                                     ETH_ALEN,
+                                                     G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+    /**
+     * NmtMacEntry:mac-address:
+     *
+     * The MAC address, as a string (as with the various #NMSetting
+     * "mac-address" properties).
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_MAC_ADDRESS,
+        g_param_spec_string("mac-address",
+                            "",
+                            "",
+                            NULL,
+                            G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+    /**
+     * NmtMacEntry:entry-type:
+     *
+     * The type of the #NmtMacEntry. Can be either used for plain
+     * MAC addresses or for the extended format for cloned MAC addresses.
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_ENTRY_TYPE,
+        g_param_spec_int("entry-type",
+                         "",
+                         "",
+                         NMT_MAC_ENTRY_TYPE_MAC,
+                         NMT_MAC_ENTRY_TYPE_CLONED,
+                         NMT_MAC_ENTRY_TYPE_MAC,
+                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+}
diff --git a/src/nmtui/nmt-mac-entry.h b/src/nmtui/nmt-mac-entry.h
new file mode 100644
index 00000000..0ed5a28c
--- /dev/null
+++ b/src/nmtui/nmt-mac-entry.h
@@ -0,0 +1,40 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+#ifndef NMT_MAC_ENTRY_H
+#define NMT_MAC_ENTRY_H
+
+#include "nm-utils.h"
+#include "libnmt-newt/nmt-newt.h"
+
+typedef enum { /*< skip >*/
+               NMT_MAC_ENTRY_TYPE_MAC,
+               NMT_MAC_ENTRY_TYPE_CLONED,
+} NmtMacEntryType;
+
+#define NMT_TYPE_MAC_ENTRY (nmt_mac_entry_get_type())
+#define NMT_MAC_ENTRY(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NMT_TYPE_MAC_ENTRY, NmtMacEntry))
+#define NMT_MAC_ENTRY_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NMT_TYPE_MAC_ENTRY, NmtMacEntryClass))
+#define NMT_IS_MAC_ENTRY(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMT_TYPE_MAC_ENTRY))
+#define NMT_IS_MAC_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NMT_TYPE_MAC_ENTRY))
+#define NMT_MAC_ENTRY_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NMT_TYPE_MAC_ENTRY, NmtMacEntryClass))
+
+typedef struct {
+    NmtNewtEntry parent;
+
+} NmtMacEntry;
+
+typedef struct {
+    NmtNewtEntryClass parent;
+
+} NmtMacEntryClass;
+
+GType nmt_mac_entry_get_type(void);
+
+NmtNewtWidget *nmt_mac_entry_new(int width, int mac_length, NmtMacEntryType type);
+
+#endif /* NMT_MAC_ENTRY_H */
diff --git a/src/nmtui/nmt-mtu-entry.c b/src/nmtui/nmt-mtu-entry.c
new file mode 100644
index 00000000..eab25a23
--- /dev/null
+++ b/src/nmtui/nmt-mtu-entry.c
@@ -0,0 +1,176 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+/**
+ * SECTION:nmt-mtu-entry
+ * @short_description: #NmtNewtEntry for MTU entry
+ *
+ * #NmtMtuEntry is an #NmtNewtEntry for entering MTU values. It will
+ * only allow typing numeric characters, and will set its
+ * #NmtNewtWidget:valid property depending on whether it currently
+ * contains a valid MTU.
+ *
+ * The entry also has an attached #NmtNewtLabel. When the entry value
+ * is "0", the label will read "(default)". Otherwise, it reads "bytes",
+ * indicating the units used by the entry.
+ */
+
+#include "libnm-client-aux-extern/nm-default-client.h"
+
+#include <stdlib.h>
+
+#include "nmt-mtu-entry.h"
+
+G_DEFINE_TYPE(NmtMtuEntry, nmt_mtu_entry, NMT_TYPE_NEWT_GRID)
+
+#define NMT_MTU_ENTRY_GET_PRIVATE(o) \
+    (G_TYPE_INSTANCE_GET_PRIVATE((o), NMT_TYPE_MTU_ENTRY, NmtMtuEntryPrivate))
+
+typedef struct {
+    int mtu;
+
+    NmtNewtEntry *entry;
+    NmtNewtLabel *label;
+
+} NmtMtuEntryPrivate;
+
+enum {
+    PROP_0,
+    PROP_MTU,
+
+    LAST_PROP
+};
+
+/**
+ * nmt_mtu_entry_new:
+ *
+ * Creates a new #NmtMtuEntry
+ *
+ * Returns: a new #NmtMtuEntry
+ */
+NmtNewtWidget *
+nmt_mtu_entry_new(void)
+{
+    return g_object_new(NMT_TYPE_MTU_ENTRY, NULL);
+}
+
+static gboolean
+mtu_validator(NmtNewtEntry *entry, const char *text, gpointer user_data)
+{
+    NmtMtuEntryPrivate *priv = NMT_MTU_ENTRY_GET_PRIVATE(user_data);
+
+    if (*text && !atoi(text)) {
+        nmt_newt_entry_set_text(entry, "");
+        text = "";
+    }
+
+    if (!*text)
+        nmt_newt_label_set_text(priv->label, _("(default)"));
+    else
+        nmt_newt_label_set_text(priv->label, _("bytes"));
+
+    return TRUE;
+}
+
+static gboolean
+mtu_transform_to_text(GBinding *    binding,
+                      const GValue *source_value,
+                      GValue *      target_value,
+                      gpointer      user_data)
+{
+    int mtu = g_value_get_int(source_value);
+
+    if (mtu)
+        g_value_transform(source_value, target_value);
+    else
+        g_value_set_string(target_value, "");
+    return TRUE;
+}
+
+static void
+nmt_mtu_entry_init(NmtMtuEntry *entry)
+{
+    NmtMtuEntryPrivate *priv = NMT_MTU_ENTRY_GET_PRIVATE(entry);
+    NmtNewtGrid *       grid = NMT_NEWT_GRID(entry);
+    NmtNewtWidget *     real_entry, *label;
+
+    real_entry  = nmt_newt_entry_numeric_new(10, 0, 65535);
+    priv->entry = NMT_NEWT_ENTRY(real_entry);
+
+    label       = nmt_newt_label_new(_("bytes"));
+    priv->label = NMT_NEWT_LABEL(label);
+
+    nmt_newt_grid_add(grid, real_entry, 0, 0);
+    nmt_newt_grid_add(grid, label, 1, 0);
+    nmt_newt_widget_set_padding(label, 1, 0, 0, 0);
+
+    nmt_newt_entry_set_validator(priv->entry, mtu_validator, entry);
+    g_object_bind_property_full(entry,
+                                "mtu",
+                                real_entry,
+                                "text",
+                                G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE,
+                                mtu_transform_to_text,
+                                NULL,
+                                NULL,
+                                NULL);
+}
+
+static void
+nmt_mtu_entry_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+    NmtMtuEntryPrivate *priv = NMT_MTU_ENTRY_GET_PRIVATE(object);
+
+    switch (prop_id) {
+    case PROP_MTU:
+        priv->mtu = g_value_get_int(value);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nmt_mtu_entry_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+    NmtMtuEntryPrivate *priv = NMT_MTU_ENTRY_GET_PRIVATE(object);
+
+    switch (prop_id) {
+    case PROP_MTU:
+        g_value_set_int(value, priv->mtu);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nmt_mtu_entry_class_init(NmtMtuEntryClass *entry_class)
+{
+    GObjectClass *object_class = G_OBJECT_CLASS(entry_class);
+
+    g_type_class_add_private(entry_class, sizeof(NmtMtuEntryPrivate));
+
+    /* virtual methods */
+    object_class->set_property = nmt_mtu_entry_set_property;
+    object_class->get_property = nmt_mtu_entry_get_property;
+
+    /**
+     * NmtMtuEntry:mtu:
+     *
+     * The contents of the entry, as a number.
+     */
+    g_object_class_install_property(object_class,
+                                    PROP_MTU,
+                                    g_param_spec_int("mtu",
+                                                     "",
+                                                     "",
+                                                     0,
+                                                     G_MAXINT,
+                                                     0,
+                                                     G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+}
diff --git a/src/nmtui/nmt-mtu-entry.h b/src/nmtui/nmt-mtu-entry.h
new file mode 100644
index 00000000..4654dabf
--- /dev/null
+++ b/src/nmtui/nmt-mtu-entry.h
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+#ifndef NMT_MTU_ENTRY_H
+#define NMT_MTU_ENTRY_H
+
+#include "libnmt-newt/nmt-newt.h"
+
+#define NMT_TYPE_MTU_ENTRY (nmt_mtu_entry_get_type())
+#define NMT_MTU_ENTRY(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NMT_TYPE_MTU_ENTRY, NmtMtuEntry))
+#define NMT_MTU_ENTRY_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NMT_TYPE_MTU_ENTRY, NmtMtuEntryClass))
+#define NMT_IS_MTU_ENTRY(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMT_TYPE_MTU_ENTRY))
+#define NMT_IS_MTU_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NMT_TYPE_MTU_ENTRY))
+#define NMT_MTU_ENTRY_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NMT_TYPE_MTU_ENTRY, NmtMtuEntryClass))
+
+typedef struct {
+    NmtNewtGrid parent;
+
+} NmtMtuEntry;
+
+typedef struct {
+    NmtNewtGridClass parent;
+
+} NmtMtuEntryClass;
+
+GType nmt_mtu_entry_get_type(void);
+
+NmtNewtWidget *nmt_mtu_entry_new(void);
+
+#endif /* NMT_MTU_ENTRY_H */
diff --git a/src/nmtui/nmt-page-bond.c b/src/nmtui/nmt-page-bond.c
new file mode 100644
index 00000000..d3fab1ff
--- /dev/null
+++ b/src/nmtui/nmt-page-bond.c
@@ -0,0 +1,445 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+/**
+ * SECTION:nmt-page-bond
+ * @short_description: The editor page for Bond connections
+ *
+ * Note that this is fairly different from most of the other editor
+ * pages, because #NMSettingBond doesn't have properties, so we
+ * can't just use #GBinding.
+ */
+
+#include "libnm-client-aux-extern/nm-default-client.h"
+
+#include "nmt-page-bond.h"
+
+#include <linux/if_ether.h>
+#include <linux/if_infiniband.h>
+
+#include "libnm-core-aux-intern/nm-libnm-core-utils.h"
+#include "nmt-mac-entry.h"
+#include "nmt-address-list.h"
+#include "nmt-slave-list.h"
+
+G_DEFINE_TYPE(NmtPageBond, nmt_page_bond, NMT_TYPE_EDITOR_PAGE_DEVICE)
+
+#define NMT_PAGE_BOND_GET_PRIVATE(o) \
+    (G_TYPE_INSTANCE_GET_PRIVATE((o), NMT_TYPE_PAGE_BOND, NmtPageBondPrivate))
+
+typedef enum {
+    NMT_PAGE_BOND_MONITORING_UNKNOWN = -1,
+    NMT_PAGE_BOND_MONITORING_MII     = 0,
+    NMT_PAGE_BOND_MONITORING_ARP     = 1,
+} NmtPageBondMonitoringMode;
+
+typedef struct {
+    NmtSlaveList *slaves;
+
+    /* Note: when adding new options to the UI also ensure they are
+     * initialized in bond_connection_setup_func()
+     */
+    NmtNewtPopup *  mode;
+    NmtNewtEntry *  primary;
+    NmtNewtPopup *  monitoring;
+    NmtNewtEntry *  miimon;
+    NmtNewtEntry *  updelay;
+    NmtNewtEntry *  downdelay;
+    NmtNewtEntry *  arp_interval;
+    NmtAddressList *arp_ip_target;
+
+    NmtPageBondMonitoringMode monitoring_mode;
+
+    NMSettingBond *s_bond;
+    GType          slave_type;
+    gboolean       updating;
+} NmtPageBondPrivate;
+
+/*****************************************************************************/
+
+static void arp_ip_target_widget_changed(GObject *object, GParamSpec *pspec, gpointer user_data);
+
+/*****************************************************************************/
+
+NmtEditorPage *
+nmt_page_bond_new(NMConnection *conn, NmtDeviceEntry *deventry)
+{
+    return g_object_new(NMT_TYPE_PAGE_BOND, "connection", conn, "device-entry", deventry, NULL);
+}
+
+static void
+nmt_page_bond_init(NmtPageBond *bond)
+{
+    NmtPageBondPrivate *priv = NMT_PAGE_BOND_GET_PRIVATE(bond);
+
+    priv->monitoring_mode = NMT_PAGE_BOND_MONITORING_UNKNOWN;
+    priv->slave_type      = G_TYPE_NONE;
+}
+
+static NmtNewtPopupEntry bond_mode[] = {
+    {N_("Round-robin"), "balance-rr"},
+    {N_("Active Backup"), "active-backup"},
+    {N_("XOR"), "balance-xor"},
+    {N_("Broadcast"), "broadcast"},
+    {N_("802.3ad"), "802.3ad"},
+    {N_("Adaptive Transmit Load Balancing (tlb)"), "balance-tlb"},
+    {N_("Adaptive Load Balancing (alb)"), "balance-alb"},
+    {NULL, NULL}};
+
+/* NB: the ordering/numbering here corresponds to NmtPageBondMonitoringMode */
+static NmtNewtPopupEntry bond_monitoring[] = {{N_("MII (recommended)"), "mii"},
+                                              {N_("ARP"), "arp"},
+                                              {NULL, NULL}};
+
+static void
+bond_options_changed(GObject *object, GParamSpec *pspec, gpointer user_data)
+{
+    NMSettingBond *      s_bond = NM_SETTING_BOND(object);
+    NmtPageBond *        bond   = NMT_PAGE_BOND(user_data);
+    NmtPageBondPrivate * priv   = NMT_PAGE_BOND_GET_PRIVATE(bond);
+    gs_free const char **ips    = NULL;
+    const char *         val;
+    gboolean             visible_mii;
+    NMBondMode           mode;
+
+    if (priv->updating)
+        return;
+
+    priv->updating = TRUE;
+
+    val = nm_setting_bond_get_option_by_name(s_bond, NM_SETTING_BOND_OPTION_MODE);
+    nmt_newt_popup_set_active_id(priv->mode, val);
+
+    mode = _nm_setting_bond_mode_from_string(val ?: "");
+
+    val = nm_setting_bond_get_option_by_name(s_bond, NM_SETTING_BOND_OPTION_PRIMARY);
+    nmt_newt_entry_set_text(priv->primary, val);
+
+    nmt_newt_widget_set_visible(NMT_NEWT_WIDGET(priv->primary), mode == NM_BOND_MODE_ACTIVEBACKUP);
+
+    if (priv->monitoring_mode == NMT_PAGE_BOND_MONITORING_UNKNOWN) {
+        val = nm_setting_bond_get_option_by_name(s_bond, NM_SETTING_BOND_OPTION_ARP_INTERVAL);
+        if (_nm_utils_ascii_str_to_int64(val, 10, 0, G_MAXINT, 0) > 0)
+            priv->monitoring_mode = NMT_PAGE_BOND_MONITORING_ARP;
+        else
+            priv->monitoring_mode = NMT_PAGE_BOND_MONITORING_MII;
+    }
+    nmt_newt_popup_set_active(priv->monitoring, priv->monitoring_mode);
+
+    val = nm_setting_bond_get_option_by_name(s_bond, NM_SETTING_BOND_OPTION_MIIMON);
+    nmt_newt_entry_set_text(priv->miimon, val ?: "0");
+
+    val = nm_setting_bond_get_option_by_name(s_bond, NM_SETTING_BOND_OPTION_UPDELAY);
+    nmt_newt_entry_set_text(priv->updelay, val ?: "0");
+
+    val = nm_setting_bond_get_option_by_name(s_bond, NM_SETTING_BOND_OPTION_DOWNDELAY);
+    nmt_newt_entry_set_text(priv->downdelay, val ?: "0");
+
+    val = nm_setting_bond_get_option_by_name(s_bond, NM_SETTING_BOND_OPTION_ARP_INTERVAL);
+    nmt_newt_entry_set_text(priv->arp_interval, val ?: "0");
+
+    val = nm_setting_bond_get_option_by_name(s_bond, NM_SETTING_BOND_OPTION_ARP_IP_TARGET);
+    ips = nm_utils_bond_option_arp_ip_targets_split(val);
+    g_object_set(G_OBJECT(priv->arp_ip_target),
+                 "strings",
+                 ips ?: NM_PTRARRAY_EMPTY(const char *),
+                 NULL);
+
+    visible_mii = (priv->monitoring_mode == NMT_PAGE_BOND_MONITORING_MII);
+
+    nmt_newt_widget_set_visible(NMT_NEWT_WIDGET(priv->miimon), visible_mii);
+    nmt_newt_widget_set_visible(NMT_NEWT_WIDGET(priv->updelay), visible_mii);
+    nmt_newt_widget_set_visible(NMT_NEWT_WIDGET(priv->downdelay), visible_mii);
+    nmt_newt_widget_set_visible(NMT_NEWT_WIDGET(priv->arp_interval), !visible_mii);
+    nmt_newt_widget_set_visible(NMT_NEWT_WIDGET(priv->arp_ip_target), !visible_mii);
+
+    priv->updating = FALSE;
+}
+
+static void
+slaves_changed(GObject *object, GParamSpec *pspec, gpointer user_data)
+{
+    NmtPageBond *       bond = NMT_PAGE_BOND(user_data);
+    NmtPageBondPrivate *priv = NMT_PAGE_BOND_GET_PRIVATE(bond);
+    GPtrArray *         slaves;
+
+    g_object_get(object, "connections", &slaves, NULL);
+    if (slaves->len == 0) {
+        if (priv->slave_type == G_TYPE_NONE)
+            return;
+        priv->slave_type = G_TYPE_NONE;
+    } else {
+        NMConnection *slave = slaves->pdata[0];
+
+        if (priv->slave_type != G_TYPE_NONE)
+            return;
+
+        if (nm_connection_is_type(slave, NM_SETTING_INFINIBAND_SETTING_NAME))
+            priv->slave_type = NM_TYPE_SETTING_INFINIBAND;
+        else
+            priv->slave_type = NM_TYPE_SETTING_WIRED;
+    }
+
+    if (priv->slave_type == NM_TYPE_SETTING_INFINIBAND) {
+        nmt_newt_popup_set_active_id(priv->mode, "active-backup");
+        nmt_newt_component_set_sensitive(NMT_NEWT_COMPONENT(priv->mode), FALSE);
+    } else
+        nmt_newt_component_set_sensitive(NMT_NEWT_COMPONENT(priv->mode), TRUE);
+}
+
+static void
+_bond_add_option(NMSettingBond *s_bond, const char *option, const char *value)
+{
+    if (nm_str_is_empty(value))
+        nm_setting_bond_remove_option(s_bond, option);
+    else
+        nm_setting_bond_add_option(s_bond, option, value);
+
+    if (nm_streq(option, NM_SETTING_BOND_OPTION_ARP_INTERVAL))
+        _nm_setting_bond_remove_options_miimon(s_bond);
+    else if (nm_streq(option, NM_SETTING_BOND_OPTION_MIIMON))
+        _nm_setting_bond_remove_options_arp_interval(s_bond);
+    nm_setting_bond_remove_option(s_bond, NM_SETTING_BOND_OPTION_ACTIVE_SLAVE);
+}
+
+#define WIDGET_CHANGED_FUNC(widget, func, option, dflt)                                         \
+    static void widget##_widget_changed(GObject *object, GParamSpec *pspec, gpointer user_data) \
+    {                                                                                           \
+        NmtPageBond *       bond = NMT_PAGE_BOND(user_data);                                    \
+        NmtPageBondPrivate *priv = NMT_PAGE_BOND_GET_PRIVATE(bond);                             \
+        const char *        v;                                                                  \
+                                                                                                \
+        if (priv->updating)                                                                     \
+            return;                                                                             \
+                                                                                                \
+        v              = func(priv->widget);                                                    \
+        priv->updating = TRUE;                                                                  \
+        _bond_add_option(priv->s_bond, option, v ?: dflt);                                      \
+        priv->updating = FALSE;                                                                 \
+    }
+
+WIDGET_CHANGED_FUNC(primary, nmt_newt_entry_get_text, NM_SETTING_BOND_OPTION_PRIMARY, NULL)
+WIDGET_CHANGED_FUNC(miimon, nmt_newt_entry_get_text, NM_SETTING_BOND_OPTION_MIIMON, "0")
+WIDGET_CHANGED_FUNC(updelay, nmt_newt_entry_get_text, NM_SETTING_BOND_OPTION_UPDELAY, "0")
+WIDGET_CHANGED_FUNC(downdelay, nmt_newt_entry_get_text, NM_SETTING_BOND_OPTION_DOWNDELAY, "0")
+WIDGET_CHANGED_FUNC(arp_interval, nmt_newt_entry_get_text, NM_SETTING_BOND_OPTION_ARP_INTERVAL, "0")
+
+static void
+mode_widget_changed(GObject *object, GParamSpec *pspec, gpointer user_data)
+{
+    NmtPageBond *       bond = NMT_PAGE_BOND(user_data);
+    NmtPageBondPrivate *priv = NMT_PAGE_BOND_GET_PRIVATE(bond);
+    const char *        mode;
+
+    if (priv->updating)
+        return;
+
+    mode           = nmt_newt_popup_get_active_id(priv->mode);
+    priv->updating = TRUE;
+    _bond_add_option(priv->s_bond, NM_SETTING_BOND_OPTION_MODE, mode);
+    priv->updating = FALSE;
+
+    if (NM_IN_STRSET(mode, "balance-tlb", "balance-alb")) {
+        nmt_newt_popup_set_active(priv->monitoring, NMT_PAGE_BOND_MONITORING_MII);
+        nmt_newt_component_set_sensitive(NMT_NEWT_COMPONENT(priv->monitoring), FALSE);
+    } else
+        nmt_newt_component_set_sensitive(NMT_NEWT_COMPONENT(priv->monitoring), TRUE);
+
+    if (NM_IN_STRSET(mode, "active-backup", "balance-alb", "balance-tlb")) {
+        nmt_newt_widget_set_visible(NMT_NEWT_WIDGET(priv->primary), TRUE);
+        _bond_add_option(priv->s_bond,
+                         NM_SETTING_BOND_OPTION_PRIMARY,
+                         nmt_newt_entry_get_text(priv->primary));
+    } else {
+        nmt_newt_widget_set_visible(NMT_NEWT_WIDGET(priv->primary), FALSE);
+        nm_setting_bond_remove_option(priv->s_bond, NM_SETTING_BOND_OPTION_PRIMARY);
+    }
+}
+
+static void
+monitoring_widget_changed(GObject *object, GParamSpec *pspec, gpointer user_data)
+{
+    NmtPageBond *       bond = NMT_PAGE_BOND(user_data);
+    NmtPageBondPrivate *priv = NMT_PAGE_BOND_GET_PRIVATE(bond);
+    gboolean            visible_mii;
+
+    if (priv->updating)
+        return;
+
+    priv->monitoring_mode = nmt_newt_popup_get_active(priv->monitoring);
+    visible_mii           = (priv->monitoring_mode == NMT_PAGE_BOND_MONITORING_MII);
+
+    nmt_newt_widget_set_visible(NMT_NEWT_WIDGET(priv->miimon), visible_mii);
+    nmt_newt_widget_set_visible(NMT_NEWT_WIDGET(priv->updelay), visible_mii);
+    nmt_newt_widget_set_visible(NMT_NEWT_WIDGET(priv->downdelay), visible_mii);
+    nmt_newt_widget_set_visible(NMT_NEWT_WIDGET(priv->arp_interval), !visible_mii);
+    nmt_newt_widget_set_visible(NMT_NEWT_WIDGET(priv->arp_ip_target), !visible_mii);
+
+    if (visible_mii) {
+        miimon_widget_changed(NULL, NULL, bond);
+        updelay_widget_changed(NULL, NULL, bond);
+        downdelay_widget_changed(NULL, NULL, bond);
+    } else {
+        arp_interval_widget_changed(NULL, NULL, bond);
+        arp_ip_target_widget_changed(NULL, NULL, bond);
+    }
+}
+
+static void
+arp_ip_target_widget_changed(GObject *object, GParamSpec *pspec, gpointer user_data)
+{
+    NmtPageBond *       bond = NMT_PAGE_BOND(user_data);
+    NmtPageBondPrivate *priv = NMT_PAGE_BOND_GET_PRIVATE(bond);
+    char **             ips, *target;
+
+    if (priv->updating)
+        return;
+
+    g_object_get(G_OBJECT(priv->arp_ip_target), "strings", &ips, NULL);
+    target = g_strjoinv(",", ips);
+
+    priv->updating = TRUE;
+    _bond_add_option(priv->s_bond, NM_SETTING_BOND_OPTION_ARP_IP_TARGET, target);
+    priv->updating = FALSE;
+
+    g_free(target);
+    g_strfreev(ips);
+}
+
+static gboolean
+bond_connection_type_filter(GType connection_type, gpointer user_data)
+{
+    NmtPageBond *       bond = user_data;
+    NmtPageBondPrivate *priv = NMT_PAGE_BOND_GET_PRIVATE(bond);
+
+    if (priv->slave_type != NM_TYPE_SETTING_WIRED && connection_type == NM_TYPE_SETTING_INFINIBAND)
+        return TRUE;
+    if (priv->slave_type != NM_TYPE_SETTING_INFINIBAND && connection_type == NM_TYPE_SETTING_WIRED)
+        return TRUE;
+
+    return FALSE;
+}
+
+static void
+nmt_page_bond_constructed(GObject *object)
+{
+    NmtPageBond *       bond = NMT_PAGE_BOND(object);
+    NmtPageBondPrivate *priv = NMT_PAGE_BOND_GET_PRIVATE(bond);
+    NmtEditorSection *  section;
+    NmtEditorGrid *     grid;
+    NMSettingWired *    s_wired;
+    NMSettingBond *     s_bond;
+    NmtNewtWidget *     widget, *label;
+    NMConnection *      conn;
+
+    conn   = nmt_editor_page_get_connection(NMT_EDITOR_PAGE(bond));
+    s_bond = nm_connection_get_setting_bond(conn);
+    if (!s_bond) {
+        nm_connection_add_setting(conn, nm_setting_bond_new());
+        s_bond = nm_connection_get_setting_bond(conn);
+    }
+    priv->s_bond = s_bond;
+
+    s_wired = nm_connection_get_setting_wired(conn);
+    if (!s_wired) {
+        nm_connection_add_setting(conn, nm_setting_wired_new());
+        s_wired = nm_connection_get_setting_wired(conn);
+    }
+
+    section = nmt_editor_section_new(_("BOND"), NULL, TRUE);
+    grid    = nmt_editor_section_get_body(section);
+
+    widget = nmt_newt_separator_new();
+    nmt_editor_grid_append(grid, _("Slaves"), widget, NULL);
+    nmt_editor_grid_set_row_flags(grid, widget, NMT_EDITOR_GRID_ROW_LABEL_ALIGN_LEFT);
+
+    widget = nmt_slave_list_new(conn, bond_connection_type_filter, bond);
+    g_signal_connect(widget, "notify::connections", G_CALLBACK(slaves_changed), bond);
+    nmt_editor_grid_append(grid, NULL, widget, NULL);
+    priv->slaves = NMT_SLAVE_LIST(widget);
+
+    widget = nmt_newt_popup_new(bond_mode);
+    g_signal_connect(widget, "notify::active-id", G_CALLBACK(mode_widget_changed), bond);
+    nmt_editor_grid_append(grid, _("Mode"), widget, NULL);
+    priv->mode = NMT_NEWT_POPUP(widget);
+
+    widget = nmt_newt_entry_new(40, 0);
+    g_signal_connect(widget, "notify::text", G_CALLBACK(primary_widget_changed), bond);
+    nmt_editor_grid_append(grid, _("Primary"), widget, NULL);
+    priv->primary = NMT_NEWT_ENTRY(widget);
+
+    widget = nmt_newt_popup_new(bond_monitoring);
+    g_signal_connect(widget, "notify::active", G_CALLBACK(monitoring_widget_changed), bond);
+    nmt_editor_grid_append(grid, _("Link monitoring"), widget, NULL);
+    priv->monitoring = NMT_NEWT_POPUP(widget);
+
+    widget = nmt_newt_entry_numeric_new(10, 0, G_MAXINT);
+    g_signal_connect(widget, "notify::text", G_CALLBACK(miimon_widget_changed), bond);
+    label = nmt_newt_label_new(C_("milliseconds", "ms"));
+    nmt_editor_grid_append(grid, _("Monitoring frequency"), widget, label);
+    priv->miimon = NMT_NEWT_ENTRY(widget);
+
+    widget = nmt_newt_entry_numeric_new(10, 0, G_MAXINT);
+    g_signal_connect(widget, "notify::text", G_CALLBACK(updelay_widget_changed), bond);
+    label = nmt_newt_label_new(C_("milliseconds", "ms"));
+    nmt_editor_grid_append(grid, _("Link up delay"), widget, label);
+    priv->updelay = NMT_NEWT_ENTRY(widget);
+
+    widget = nmt_newt_entry_numeric_new(10, 0, G_MAXINT);
+    g_signal_connect(widget, "notify::text", G_CALLBACK(downdelay_widget_changed), bond);
+    label = nmt_newt_label_new(C_("milliseconds", "ms"));
+    nmt_editor_grid_append(grid, _("Link down delay"), widget, label);
+    priv->downdelay = NMT_NEWT_ENTRY(widget);
+
+    widget = nmt_newt_entry_numeric_new(10, 0, G_MAXINT);
+    g_signal_connect(widget, "notify::text", G_CALLBACK(arp_interval_widget_changed), bond);
+    label = nmt_newt_label_new(C_("milliseconds", "ms"));
+    nmt_editor_grid_append(grid, _("Monitoring frequency"), widget, label);
+    priv->arp_interval = NMT_NEWT_ENTRY(widget);
+
+    widget = nmt_address_list_new(NMT_ADDRESS_LIST_IP4);
+    g_signal_connect(widget, "notify::strings", G_CALLBACK(arp_ip_target_widget_changed), bond);
+    nmt_editor_grid_append(grid, _("ARP targets"), widget, NULL);
+    priv->arp_ip_target = NMT_ADDRESS_LIST(widget);
+
+    widget = nmt_mac_entry_new(40, ETH_ALEN, NMT_MAC_ENTRY_TYPE_CLONED);
+    g_object_bind_property(s_wired,
+                           NM_SETTING_WIRED_CLONED_MAC_ADDRESS,
+                           widget,
+                           "mac-address",
+                           G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+    nmt_editor_grid_append(grid, _("Cloned MAC address"), widget, NULL);
+
+    g_signal_connect(s_bond,
+                     "notify::" NM_SETTING_BOND_OPTIONS,
+                     G_CALLBACK(bond_options_changed),
+                     bond);
+    bond_options_changed(G_OBJECT(s_bond), NULL, bond);
+    slaves_changed(G_OBJECT(priv->slaves), NULL, bond);
+
+    nmt_editor_page_add_section(NMT_EDITOR_PAGE(bond), section);
+
+    G_OBJECT_CLASS(nmt_page_bond_parent_class)->constructed(object);
+}
+
+static void
+nmt_page_bond_saved(NmtEditorPage *editor_page)
+{
+    NmtPageBondPrivate *priv = NMT_PAGE_BOND_GET_PRIVATE(editor_page);
+
+    nmt_edit_connection_list_recommit(NMT_EDIT_CONNECTION_LIST(priv->slaves));
+}
+
+static void
+nmt_page_bond_class_init(NmtPageBondClass *bond_class)
+{
+    GObjectClass *      object_class      = G_OBJECT_CLASS(bond_class);
+    NmtEditorPageClass *editor_page_class = NMT_EDITOR_PAGE_CLASS(bond_class);
+
+    g_type_class_add_private(bond_class, sizeof(NmtPageBondPrivate));
+
+    object_class->constructed = nmt_page_bond_constructed;
+    editor_page_class->saved  = nmt_page_bond_saved;
+}
diff --git a/src/nmtui/nmt-page-bond.h b/src/nmtui/nmt-page-bond.h
new file mode 100644
index 00000000..9481ec45
--- /dev/null
+++ b/src/nmtui/nmt-page-bond.h
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+#ifndef NMT_PAGE_BOND_H
+#define NMT_PAGE_BOND_H
+
+#include "nmt-editor-page-device.h"
+
+#define NMT_TYPE_PAGE_BOND (nmt_page_bond_get_type())
+#define NMT_PAGE_BOND(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NMT_TYPE_PAGE_BOND, NmtPageBond))
+#define NMT_PAGE_BOND_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NMT_TYPE_PAGE_BOND, NmtPageBondClass))
+#define NMT_IS_PAGE_BOND(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMT_TYPE_PAGE_BOND))
+#define NMT_IS_PAGE_BOND_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NMT_TYPE_PAGE_BOND))
+#define NMT_PAGE_BOND_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NMT_TYPE_PAGE_BOND, NmtPageBondClass))
+
+typedef struct {
+    NmtEditorPageDevice parent;
+
+} NmtPageBond;
+
+typedef struct {
+    NmtEditorPageDeviceClass parent;
+
+} NmtPageBondClass;
+
+GType nmt_page_bond_get_type(void);
+
+NmtEditorPage *nmt_page_bond_new(NMConnection *conn, NmtDeviceEntry *deventry);
+
+#endif /* NMT_PAGE_BOND_H */
diff --git a/src/nmtui/nmt-page-bridge-port.c b/src/nmtui/nmt-page-bridge-port.c
new file mode 100644
index 00000000..30163d15
--- /dev/null
+++ b/src/nmtui/nmt-page-bridge-port.c
@@ -0,0 +1,82 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+/**
+ * SECTION:nmt-page-bridge-port
+ * @short_description: The editor page for Bridge ports
+ */
+
+#include "libnm-client-aux-extern/nm-default-client.h"
+
+#include "nmt-page-bridge-port.h"
+
+G_DEFINE_TYPE(NmtPageBridgePort, nmt_page_bridge_port, NMT_TYPE_EDITOR_PAGE)
+
+NmtEditorPage *
+nmt_page_bridge_port_new(NMConnection *conn)
+{
+    return g_object_new(NMT_TYPE_PAGE_BRIDGE_PORT, "connection", conn, NULL);
+}
+
+static void
+nmt_page_bridge_port_init(NmtPageBridgePort *bridge)
+{}
+
+static void
+nmt_page_bridge_port_constructed(GObject *object)
+{
+    NmtPageBridgePort *  bridge = NMT_PAGE_BRIDGE_PORT(object);
+    NmtEditorSection *   section;
+    NmtEditorGrid *      grid;
+    NMSettingBridgePort *s_port;
+    NmtNewtWidget *      widget;
+    NMConnection *       conn;
+
+    conn   = nmt_editor_page_get_connection(NMT_EDITOR_PAGE(bridge));
+    s_port = nm_connection_get_setting_bridge_port(conn);
+    if (!s_port) {
+        nm_connection_add_setting(conn, nm_setting_bridge_port_new());
+        s_port = nm_connection_get_setting_bridge_port(conn);
+    }
+
+    section = nmt_editor_section_new(_("BRIDGE PORT"), NULL, TRUE);
+    grid    = nmt_editor_section_get_body(section);
+
+    widget = nmt_newt_entry_numeric_new(10, 0, 63);
+    g_object_bind_property(s_port,
+                           NM_SETTING_BRIDGE_PORT_PRIORITY,
+                           widget,
+                           "text",
+                           G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+    nmt_editor_grid_append(grid, _("Priority"), widget, NULL);
+
+    widget = nmt_newt_entry_numeric_new(10, 1, 65535);
+    g_object_bind_property(s_port,
+                           NM_SETTING_BRIDGE_PORT_PATH_COST,
+                           widget,
+                           "text",
+                           G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+    nmt_editor_grid_append(grid, _("Path cost"), widget, NULL);
+
+    widget = nmt_newt_checkbox_new(_("Hairpin mode"));
+    g_object_bind_property(s_port,
+                           NM_SETTING_BRIDGE_PORT_HAIRPIN_MODE,
+                           widget,
+                           "active",
+                           G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+    nmt_editor_grid_append(grid, NULL, widget, NULL);
+
+    nmt_editor_page_add_section(NMT_EDITOR_PAGE(bridge), section);
+
+    G_OBJECT_CLASS(nmt_page_bridge_port_parent_class)->constructed(object);
+}
+
+static void
+nmt_page_bridge_port_class_init(NmtPageBridgePortClass *bridge_class)
+{
+    GObjectClass *object_class = G_OBJECT_CLASS(bridge_class);
+
+    object_class->constructed = nmt_page_bridge_port_constructed;
+}
diff --git a/src/nmtui/nmt-page-bridge-port.h b/src/nmtui/nmt-page-bridge-port.h
new file mode 100644
index 00000000..dc6a479c
--- /dev/null
+++ b/src/nmtui/nmt-page-bridge-port.h
@@ -0,0 +1,36 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+#ifndef NMT_PAGE_BRIDGE_PORT_H
+#define NMT_PAGE_BRIDGE_PORT_H
+
+#include "nmt-editor-page.h"
+
+#define NMT_TYPE_PAGE_BRIDGE_PORT (nmt_page_bridge_port_get_type())
+#define NMT_PAGE_BRIDGE_PORT(obj) \
+    (G_TYPE_CHECK_INSTANCE_CAST((obj), NMT_TYPE_PAGE_BRIDGE_PORT, NmtPageBridgePort))
+#define NMT_PAGE_BRIDGE_PORT_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NMT_TYPE_PAGE_BRIDGE_PORT, NmtPageBridgePortClass))
+#define NMT_IS_PAGE_BRIDGE_PORT(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMT_TYPE_PAGE_BRIDGE_PORT))
+#define NMT_IS_PAGE_BRIDGE_PORT_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_TYPE((klass), NMT_TYPE_PAGE_BRIDGE_PORT))
+#define NMT_PAGE_BRIDGE_PORT_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NMT_TYPE_PAGE_BRIDGE_PORT, NmtPageBridgePortClass))
+
+typedef struct {
+    NmtEditorPage parent;
+
+} NmtPageBridgePort;
+
+typedef struct {
+    NmtEditorPageClass parent;
+
+} NmtPageBridgePortClass;
+
+GType nmt_page_bridge_port_get_type(void);
+
+NmtEditorPage *nmt_page_bridge_port_new(NMConnection *conn);
+
+#endif /* NMT_PAGE_BRIDGE_PORT_H */
diff --git a/src/nmtui/nmt-page-bridge.c b/src/nmtui/nmt-page-bridge.c
new file mode 100644
index 00000000..52af8db1
--- /dev/null
+++ b/src/nmtui/nmt-page-bridge.c
@@ -0,0 +1,182 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+/**
+ * SECTION:nmt-page-bridge
+ * @short_description: The editor page for Bridge connections
+ */
+
+#include "libnm-client-aux-extern/nm-default-client.h"
+
+#include "nmt-page-bridge.h"
+
+#include "nmt-address-list.h"
+#include "nmt-slave-list.h"
+
+G_DEFINE_TYPE(NmtPageBridge, nmt_page_bridge, NMT_TYPE_EDITOR_PAGE_DEVICE)
+
+#define NMT_PAGE_BRIDGE_GET_PRIVATE(o) \
+    (G_TYPE_INSTANCE_GET_PRIVATE((o), NMT_TYPE_PAGE_BRIDGE, NmtPageBridgePrivate))
+
+typedef struct {
+    NmtSlaveList *slaves;
+} NmtPageBridgePrivate;
+
+NmtEditorPage *
+nmt_page_bridge_new(NMConnection *conn, NmtDeviceEntry *deventry)
+{
+    return g_object_new(NMT_TYPE_PAGE_BRIDGE, "connection", conn, "device-entry", deventry, NULL);
+}
+
+static void
+nmt_page_bridge_init(NmtPageBridge *bridge)
+{}
+
+static gboolean
+bridge_connection_type_filter(GType connection_type, gpointer user_data)
+{
+    return (connection_type == NM_TYPE_SETTING_WIRED || connection_type == NM_TYPE_SETTING_WIRELESS
+            || connection_type == NM_TYPE_SETTING_VLAN);
+}
+
+static void
+nmt_page_bridge_constructed(GObject *object)
+{
+    NmtPageBridge *       bridge = NMT_PAGE_BRIDGE(object);
+    NmtPageBridgePrivate *priv   = NMT_PAGE_BRIDGE_GET_PRIVATE(bridge);
+    NmtEditorSection *    section;
+    NmtEditorGrid *       grid;
+    NMSettingBridge *     s_bridge;
+    NmtNewtWidget *       widget, *label, *stp;
+    NMConnection *        conn;
+
+    conn     = nmt_editor_page_get_connection(NMT_EDITOR_PAGE(bridge));
+    s_bridge = nm_connection_get_setting_bridge(conn);
+    if (!s_bridge) {
+        nm_connection_add_setting(conn, nm_setting_bridge_new());
+        s_bridge = nm_connection_get_setting_bridge(conn);
+    }
+
+    section = nmt_editor_section_new(_("BRIDGE"), NULL, TRUE);
+    grid    = nmt_editor_section_get_body(section);
+
+    widget = nmt_newt_separator_new();
+    nmt_editor_grid_append(grid, _("Slaves"), widget, NULL);
+    nmt_editor_grid_set_row_flags(grid, widget, NMT_EDITOR_GRID_ROW_LABEL_ALIGN_LEFT);
+
+    widget = nmt_slave_list_new(conn, bridge_connection_type_filter, bridge);
+    nmt_editor_grid_append(grid, NULL, widget, NULL);
+    priv->slaves = NMT_SLAVE_LIST(widget);
+
+    widget = nmt_newt_entry_numeric_new(10, 0, 1000000);
+    g_object_bind_property(s_bridge,
+                           NM_SETTING_BRIDGE_AGEING_TIME,
+                           widget,
+                           "text",
+                           G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+    label = nmt_newt_label_new(_("seconds"));
+    nmt_editor_grid_append(grid, _("Aging time"), widget, label);
+
+    widget = nmt_newt_checkbox_new(_("Enable IGMP snooping"));
+    g_object_bind_property(s_bridge,
+                           NM_SETTING_BRIDGE_MULTICAST_SNOOPING,
+                           widget,
+                           "active",
+                           G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+    nmt_editor_grid_append(grid, NULL, widget, NULL);
+
+    widget = stp = nmt_newt_checkbox_new(_("Enable STP (Spanning Tree Protocol)"));
+    g_object_bind_property(s_bridge,
+                           NM_SETTING_BRIDGE_STP,
+                           widget,
+                           "active",
+                           G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+    nmt_editor_grid_append(grid, NULL, widget, NULL);
+
+    widget = nmt_newt_entry_numeric_new(10, 0, G_MAXINT);
+    g_object_bind_property(s_bridge,
+                           NM_SETTING_BRIDGE_PRIORITY,
+                           widget,
+                           "text",
+                           G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+    g_object_bind_property(s_bridge,
+                           NM_SETTING_BRIDGE_STP,
+                           widget,
+                           "sensitive",
+                           G_BINDING_SYNC_CREATE);
+    nmt_editor_grid_append(grid, _("Priority"), widget, NULL);
+
+    widget = nmt_newt_entry_numeric_new(10, 2, 30);
+    g_object_bind_property(s_bridge,
+                           NM_SETTING_BRIDGE_FORWARD_DELAY,
+                           widget,
+                           "text",
+                           G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+    g_object_bind_property(s_bridge,
+                           NM_SETTING_BRIDGE_STP,
+                           widget,
+                           "sensitive",
+                           G_BINDING_SYNC_CREATE);
+    label = nmt_newt_label_new(_("seconds"));
+    nmt_editor_grid_append(grid, _("Forward delay"), widget, label);
+
+    widget = nmt_newt_entry_numeric_new(10, 1, 10);
+    g_object_bind_property(s_bridge,
+                           NM_SETTING_BRIDGE_HELLO_TIME,
+                           widget,
+                           "text",
+                           G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+    g_object_bind_property(s_bridge,
+                           NM_SETTING_BRIDGE_STP,
+                           widget,
+                           "sensitive",
+                           G_BINDING_SYNC_CREATE);
+    label = nmt_newt_label_new(_("seconds"));
+    nmt_editor_grid_append(grid, _("Hello time"), widget, label);
+
+    widget = nmt_newt_entry_numeric_new(10, 6, 40);
+    g_object_bind_property(s_bridge,
+                           NM_SETTING_BRIDGE_MAX_AGE,
+                           widget,
+                           "text",
+                           G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+    g_object_bind_property(s_bridge,
+                           NM_SETTING_BRIDGE_STP,
+                           widget,
+                           "sensitive",
+                           G_BINDING_SYNC_CREATE);
+    label = nmt_newt_label_new(_("seconds"));
+    nmt_editor_grid_append(grid, _("Max age"), widget, label);
+
+    widget = nmt_newt_entry_numeric_new(10, 0, 65535);
+    g_object_bind_property(s_bridge,
+                           NM_SETTING_BRIDGE_GROUP_FORWARD_MASK,
+                           widget,
+                           "text",
+                           G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+    nmt_editor_grid_append(grid, _("Group forward mask"), widget, NULL);
+
+    nmt_editor_page_add_section(NMT_EDITOR_PAGE(bridge), section);
+
+    G_OBJECT_CLASS(nmt_page_bridge_parent_class)->constructed(object);
+}
+
+static void
+nmt_page_bridge_saved(NmtEditorPage *editor_page)
+{
+    NmtPageBridgePrivate *priv = NMT_PAGE_BRIDGE_GET_PRIVATE(editor_page);
+
+    nmt_edit_connection_list_recommit(NMT_EDIT_CONNECTION_LIST(priv->slaves));
+}
+
+static void
+nmt_page_bridge_class_init(NmtPageBridgeClass *bridge_class)
+{
+    GObjectClass *      object_class      = G_OBJECT_CLASS(bridge_class);
+    NmtEditorPageClass *editor_page_class = NMT_EDITOR_PAGE_CLASS(bridge_class);
+
+    object_class->constructed = nmt_page_bridge_constructed;
+    editor_page_class->saved  = nmt_page_bridge_saved;
+}
diff --git a/src/nmtui/nmt-page-bridge.h b/src/nmtui/nmt-page-bridge.h
new file mode 100644
index 00000000..c5c7a39c
--- /dev/null
+++ b/src/nmtui/nmt-page-bridge.h
@@ -0,0 +1,35 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+#ifndef NMT_PAGE_BRIDGE_H
+#define NMT_PAGE_BRIDGE_H
+
+#include "nmt-editor-page-device.h"
+
+#define NMT_TYPE_PAGE_BRIDGE (nmt_page_bridge_get_type())
+#define NMT_PAGE_BRIDGE(obj) \
+    (G_TYPE_CHECK_INSTANCE_CAST((obj), NMT_TYPE_PAGE_BRIDGE, NmtPageBridge))
+#define NMT_PAGE_BRIDGE_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NMT_TYPE_PAGE_BRIDGE, NmtPageBridgeClass))
+#define NMT_IS_PAGE_BRIDGE(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMT_TYPE_PAGE_BRIDGE))
+#define NMT_IS_PAGE_BRIDGE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NMT_TYPE_PAGE_BRIDGE))
+#define NMT_PAGE_BRIDGE_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NMT_TYPE_PAGE_BRIDGE, NmtPageBridgeClass))
+
+typedef struct {
+    NmtEditorPageDevice parent;
+
+} NmtPageBridge;
+
+typedef struct {
+    NmtEditorPageDeviceClass parent;
+
+} NmtPageBridgeClass;
+
+GType nmt_page_bridge_get_type(void);
+
+NmtEditorPage *nmt_page_bridge_new(NMConnection *conn, NmtDeviceEntry *deventry);
+
+#endif /* NMT_PAGE_BRIDGE_H */
diff --git a/src/nmtui/nmt-page-dsl.c b/src/nmtui/nmt-page-dsl.c
new file mode 100644
index 00000000..d7e0d144
--- /dev/null
+++ b/src/nmtui/nmt-page-dsl.c
@@ -0,0 +1,131 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2014 Red Hat, Inc.
+ */
+
+/**
+ * SECTION:nmt-page-dsl
+ * @short_description: The editor page for DSL connections
+ */
+
+#include "libnm-client-aux-extern/nm-default-client.h"
+
+#include "nmt-page-dsl.h"
+#include "nmt-page-ethernet.h"
+#include "nmt-page-ppp.h"
+#include "nmt-password-fields.h"
+
+G_DEFINE_TYPE(NmtPageDsl, nmt_page_dsl, NMT_TYPE_EDITOR_PAGE_DEVICE)
+
+#define NMT_PAGE_DSL_GET_PRIVATE(o) \
+    (G_TYPE_INSTANCE_GET_PRIVATE((o), NMT_TYPE_PAGE_DSL, NmtPageDslPrivate))
+
+typedef struct {
+    NmtEditorPage *ethernet_page, *ppp_page;
+
+} NmtPageDslPrivate;
+
+NmtEditorPage *
+nmt_page_dsl_new(NMConnection *conn, NmtDeviceEntry *deventry)
+{
+    return g_object_new(NMT_TYPE_PAGE_DSL, "connection", conn, "device-entry", deventry, NULL);
+}
+
+static void
+nmt_page_dsl_init(NmtPageDsl *dsl)
+{}
+
+static NmtEditorSection *
+build_dsl_section(NmtPageDsl *dsl, NMSettingPppoe *s_pppoe)
+{
+    NmtEditorSection *section;
+    NmtEditorGrid *   grid;
+    NmtNewtWidget *   widget;
+
+    section = nmt_editor_section_new(_("DSL"), NULL, TRUE);
+    grid    = nmt_editor_section_get_body(section);
+
+    widget = nmt_newt_entry_new(40, 0);
+    nmt_editor_grid_append(grid, _("Username"), widget, NULL);
+    g_object_bind_property(s_pppoe,
+                           NM_SETTING_PPPOE_USERNAME,
+                           widget,
+                           "text",
+                           G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
+
+    widget = nmt_password_fields_new(40, NMT_PASSWORD_FIELDS_SHOW_PASSWORD);
+    g_object_bind_property(s_pppoe,
+                           NM_SETTING_PPPOE_PASSWORD,
+                           widget,
+                           "password",
+                           G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
+    nmt_editor_grid_append(grid, _("Password"), widget, NULL);
+
+    widget = nmt_newt_entry_new(40, 0);
+    nmt_editor_grid_append(grid, _("Service"), widget, NULL);
+    g_object_bind_property(s_pppoe,
+                           NM_SETTING_PPPOE_SERVICE,
+                           widget,
+                           "text",
+                           G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
+
+    return section;
+}
+
+static void
+nmt_page_dsl_constructed(GObject *object)
+{
+    NmtPageDsl *       dsl  = NMT_PAGE_DSL(object);
+    NmtPageDslPrivate *priv = NMT_PAGE_DSL_GET_PRIVATE(dsl);
+    NMConnection *     conn;
+    NMSettingPppoe *   s_pppoe;
+    NmtEditorSection * section;
+    const GSList *     sections, *iter;
+
+    conn    = nmt_editor_page_get_connection(NMT_EDITOR_PAGE(dsl));
+    s_pppoe = nm_connection_get_setting_pppoe(conn);
+    if (!s_pppoe) {
+        nm_connection_add_setting(conn, nm_setting_pppoe_new());
+        s_pppoe = nm_connection_get_setting_pppoe(conn);
+    }
+
+    section = build_dsl_section(dsl, s_pppoe);
+    nmt_editor_page_add_section(NMT_EDITOR_PAGE(dsl), section);
+
+    priv->ethernet_page =
+        nmt_page_ethernet_new(conn,
+                              nmt_editor_page_device_get_device_entry(NMT_EDITOR_PAGE_DEVICE(dsl)));
+    sections = nmt_editor_page_get_sections(priv->ethernet_page);
+    for (iter = sections; iter; iter = iter->next)
+        nmt_editor_page_add_section(NMT_EDITOR_PAGE(dsl), iter->data);
+
+    priv->ppp_page = nmt_page_ppp_new(conn);
+    sections       = nmt_editor_page_get_sections(priv->ppp_page);
+    for (iter = sections; iter; iter = iter->next)
+        nmt_editor_page_add_section(NMT_EDITOR_PAGE(dsl), iter->data);
+
+    G_OBJECT_CLASS(nmt_page_dsl_parent_class)->constructed(object);
+}
+
+static void
+nmt_page_dsl_finalize(GObject *object)
+{
+    NmtPageDsl *       dsl  = NMT_PAGE_DSL(object);
+    NmtPageDslPrivate *priv = NMT_PAGE_DSL_GET_PRIVATE(dsl);
+
+    g_clear_object(&priv->ethernet_page);
+    g_clear_object(&priv->ppp_page);
+
+    G_OBJECT_CLASS(nmt_page_dsl_parent_class)->finalize(object);
+}
+
+static void
+nmt_page_dsl_class_init(NmtPageDslClass *dsl_class)
+{
+    GObjectClass *object_class = G_OBJECT_CLASS(dsl_class);
+
+    g_type_class_add_private(object_class, sizeof(NmtPageDslPrivate));
+
+    object_class->constructed = nmt_page_dsl_constructed;
+    object_class->finalize    = nmt_page_dsl_finalize;
+}
diff --git a/src/nmtui/nmt-page-dsl.h b/src/nmtui/nmt-page-dsl.h
new file mode 100644
index 00000000..e14d67be
--- /dev/null
+++ b/src/nmtui/nmt-page-dsl.h
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+#ifndef NMT_PAGE_DSL_H
+#define NMT_PAGE_DSL_H
+
+#include "nmt-editor-page-device.h"
+
+#define NMT_TYPE_PAGE_DSL (nmt_page_dsl_get_type())
+#define NMT_PAGE_DSL(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NMT_TYPE_PAGE_DSL, NmtPageDsl))
+#define NMT_PAGE_DSL_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NMT_TYPE_PAGE_DSL, NmtPageDslClass))
+#define NMT_IS_PAGE_DSL(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMT_TYPE_PAGE_DSL))
+#define NMT_IS_PAGE_DSL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NMT_TYPE_PAGE_DSL))
+#define NMT_PAGE_DSL_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NMT_TYPE_PAGE_DSL, NmtPageDslClass))
+
+typedef struct {
+    NmtEditorPageDevice parent;
+
+} NmtPageDsl;
+
+typedef struct {
+    NmtEditorPageDeviceClass parent;
+
+} NmtPageDslClass;
+
+GType nmt_page_dsl_get_type(void);
+
+NmtEditorPage *nmt_page_dsl_new(NMConnection *conn, NmtDeviceEntry *deventry);
+
+#endif /* NMT_PAGE_DSL_H */
diff --git a/src/nmtui/nmt-page-ethernet.c b/src/nmtui/nmt-page-ethernet.c
new file mode 100644
index 00000000..f5dc9119
--- /dev/null
+++ b/src/nmtui/nmt-page-ethernet.c
@@ -0,0 +1,88 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+/**
+ * SECTION:nmt-page-ethernet
+ * @short_description: The editor page for Ethernet connections
+ */
+
+#include "libnm-client-aux-extern/nm-default-client.h"
+
+#include "nmt-page-ethernet.h"
+
+#include <linux/if_ether.h>
+#include <linux/if_infiniband.h>
+
+#include "nmt-mac-entry.h"
+#include "nmt-mtu-entry.h"
+
+G_DEFINE_TYPE(NmtPageEthernet, nmt_page_ethernet, NMT_TYPE_EDITOR_PAGE_DEVICE)
+
+NmtEditorPage *
+nmt_page_ethernet_new(NMConnection *conn, NmtDeviceEntry *deventry)
+{
+    return g_object_new(NMT_TYPE_PAGE_ETHERNET, "connection", conn, "device-entry", deventry, NULL);
+}
+
+static void
+nmt_page_ethernet_init(NmtPageEthernet *ethernet)
+{}
+
+static void
+nmt_page_ethernet_constructed(GObject *object)
+{
+    NmtPageEthernet * ethernet = NMT_PAGE_ETHERNET(object);
+    NmtDeviceEntry *  deventry;
+    NmtEditorSection *section;
+    NmtEditorGrid *   grid;
+    NMSettingWired *  s_wired;
+    NmtNewtWidget *   widget;
+    NMConnection *    conn;
+
+    conn    = nmt_editor_page_get_connection(NMT_EDITOR_PAGE(ethernet));
+    s_wired = nm_connection_get_setting_wired(conn);
+    if (!s_wired) {
+        nm_connection_add_setting(conn, nm_setting_wired_new());
+        s_wired = nm_connection_get_setting_wired(conn);
+    }
+
+    deventry = nmt_editor_page_device_get_device_entry(NMT_EDITOR_PAGE_DEVICE(object));
+    g_object_bind_property(s_wired,
+                           NM_SETTING_WIRED_MAC_ADDRESS,
+                           deventry,
+                           "mac-address",
+                           G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+
+    section = nmt_editor_section_new(_("ETHERNET"), NULL, FALSE);
+    grid    = nmt_editor_section_get_body(section);
+
+    widget = nmt_mac_entry_new(40, ETH_ALEN, NMT_MAC_ENTRY_TYPE_CLONED);
+    g_object_bind_property(s_wired,
+                           NM_SETTING_WIRED_CLONED_MAC_ADDRESS,
+                           widget,
+                           "mac-address",
+                           G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+    nmt_editor_grid_append(grid, _("Cloned MAC address"), widget, NULL);
+
+    widget = nmt_mtu_entry_new();
+    g_object_bind_property(s_wired,
+                           NM_SETTING_WIRED_MTU,
+                           widget,
+                           "mtu",
+                           G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+    nmt_editor_grid_append(grid, _("MTU"), widget, NULL);
+
+    nmt_editor_page_add_section(NMT_EDITOR_PAGE(ethernet), section);
+
+    G_OBJECT_CLASS(nmt_page_ethernet_parent_class)->constructed(object);
+}
+
+static void
+nmt_page_ethernet_class_init(NmtPageEthernetClass *ethernet_class)
+{
+    GObjectClass *object_class = G_OBJECT_CLASS(ethernet_class);
+
+    object_class->constructed = nmt_page_ethernet_constructed;
+}
diff --git a/src/nmtui/nmt-page-ethernet.h b/src/nmtui/nmt-page-ethernet.h
new file mode 100644
index 00000000..f7e6cf15
--- /dev/null
+++ b/src/nmtui/nmt-page-ethernet.h
@@ -0,0 +1,35 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+#ifndef NMT_PAGE_ETHERNET_H
+#define NMT_PAGE_ETHERNET_H
+
+#include "nmt-editor-page-device.h"
+
+#define NMT_TYPE_PAGE_ETHERNET (nmt_page_ethernet_get_type())
+#define NMT_PAGE_ETHERNET(obj) \
+    (G_TYPE_CHECK_INSTANCE_CAST((obj), NMT_TYPE_PAGE_ETHERNET, NmtPageEthernet))
+#define NMT_PAGE_ETHERNET_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NMT_TYPE_PAGE_ETHERNET, NmtPageEthernetClass))
+#define NMT_IS_PAGE_ETHERNET(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMT_TYPE_PAGE_ETHERNET))
+#define NMT_IS_PAGE_ETHERNET_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NMT_TYPE_PAGE_ETHERNET))
+#define NMT_PAGE_ETHERNET_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NMT_TYPE_PAGE_ETHERNET, NmtPageEthernetClass))
+
+typedef struct {
+    NmtEditorPageDevice parent;
+
+} NmtPageEthernet;
+
+typedef struct {
+    NmtEditorPageDeviceClass parent;
+
+} NmtPageEthernetClass;
+
+GType nmt_page_ethernet_get_type(void);
+
+NmtEditorPage *nmt_page_ethernet_new(NMConnection *conn, NmtDeviceEntry *deventry);
+
+#endif /* NMT_PAGE_ETHERNET_H */
diff --git a/src/nmtui/nmt-page-infiniband.c b/src/nmtui/nmt-page-infiniband.c
new file mode 100644
index 00000000..c0e3611b
--- /dev/null
+++ b/src/nmtui/nmt-page-infiniband.c
@@ -0,0 +1,96 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+/**
+ * SECTION:nmt-page-infiniband
+ * @short_description: The editor page for InfiniBand connections
+ */
+
+#include "libnm-client-aux-extern/nm-default-client.h"
+
+#include "nmt-page-infiniband.h"
+#include "nmt-mtu-entry.h"
+
+G_DEFINE_TYPE(NmtPageInfiniband, nmt_page_infiniband, NMT_TYPE_EDITOR_PAGE_DEVICE)
+
+NmtEditorPage *
+nmt_page_infiniband_new(NMConnection *conn, NmtDeviceEntry *deventry)
+{
+    return g_object_new(NMT_TYPE_PAGE_INFINIBAND,
+                        "connection",
+                        conn,
+                        "device-entry",
+                        deventry,
+                        NULL);
+}
+
+static void
+nmt_page_infiniband_init(NmtPageInfiniband *infiniband)
+{}
+
+static NmtNewtPopupEntry transport_mode[] = {{N_("Datagram"), "datagram"},
+                                             {N_("Connected"), "connected"},
+                                             {NULL, NULL}};
+
+static void
+nmt_page_infiniband_constructed(GObject *object)
+{
+    NmtPageInfiniband *  infiniband = NMT_PAGE_INFINIBAND(object);
+    NmtDeviceEntry *     deventry;
+    NmtEditorSection *   section;
+    NmtEditorGrid *      grid;
+    NMSettingInfiniband *s_ib;
+    NmtNewtWidget *      widget;
+    NMConnection *       conn;
+
+    conn = nmt_editor_page_get_connection(NMT_EDITOR_PAGE(infiniband));
+    s_ib = nm_connection_get_setting_infiniband(conn);
+    if (!s_ib) {
+        nm_connection_add_setting(conn, nm_setting_infiniband_new());
+        s_ib = nm_connection_get_setting_infiniband(conn);
+    }
+    /* initialize 'transport-mode' if it is NULL */
+    if (!nm_setting_infiniband_get_transport_mode(s_ib)) {
+        g_object_set(G_OBJECT(s_ib), NM_SETTING_INFINIBAND_TRANSPORT_MODE, "datagram", NULL);
+    }
+
+    deventry = nmt_editor_page_device_get_device_entry(NMT_EDITOR_PAGE_DEVICE(object));
+    g_object_bind_property(s_ib,
+                           NM_SETTING_INFINIBAND_MAC_ADDRESS,
+                           deventry,
+                           "mac-address",
+                           G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+
+    section = nmt_editor_section_new(_("INFINIBAND"), NULL, TRUE);
+    grid    = nmt_editor_section_get_body(section);
+
+    widget = nmt_newt_popup_new(transport_mode);
+    g_object_bind_property(s_ib,
+                           NM_SETTING_INFINIBAND_TRANSPORT_MODE,
+                           widget,
+                           "active-id",
+                           G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+    nmt_editor_grid_append(grid, _("Transport mode"), widget, NULL);
+
+    widget = nmt_mtu_entry_new();
+    g_object_bind_property(s_ib,
+                           NM_SETTING_INFINIBAND_MTU,
+                           widget,
+                           "mtu",
+                           G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+    nmt_editor_grid_append(grid, _("MTU"), widget, NULL);
+
+    nmt_editor_page_add_section(NMT_EDITOR_PAGE(infiniband), section);
+
+    G_OBJECT_CLASS(nmt_page_infiniband_parent_class)->constructed(object);
+}
+
+static void
+nmt_page_infiniband_class_init(NmtPageInfinibandClass *infiniband_class)
+{
+    GObjectClass *object_class = G_OBJECT_CLASS(infiniband_class);
+
+    object_class->constructed = nmt_page_infiniband_constructed;
+}
diff --git a/src/nmtui/nmt-page-infiniband.h b/src/nmtui/nmt-page-infiniband.h
new file mode 100644
index 00000000..da8fb3d6
--- /dev/null
+++ b/src/nmtui/nmt-page-infiniband.h
@@ -0,0 +1,36 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+#ifndef NMT_PAGE_INFINIBAND_H
+#define NMT_PAGE_INFINIBAND_H
+
+#include "nmt-editor-page-device.h"
+
+#define NMT_TYPE_PAGE_INFINIBAND (nmt_page_infiniband_get_type())
+#define NMT_PAGE_INFINIBAND(obj) \
+    (G_TYPE_CHECK_INSTANCE_CAST((obj), NMT_TYPE_PAGE_INFINIBAND, NmtPageInfiniband))
+#define NMT_PAGE_INFINIBAND_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NMT_TYPE_PAGE_INFINIBAND, NmtPageInfinibandClass))
+#define NMT_IS_PAGE_INFINIBAND(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMT_TYPE_PAGE_INFINIBAND))
+#define NMT_IS_PAGE_INFINIBAND_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_TYPE((klass), NMT_TYPE_PAGE_INFINIBAND))
+#define NMT_PAGE_INFINIBAND_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NMT_TYPE_PAGE_INFINIBAND, NmtPageInfinibandClass))
+
+typedef struct {
+    NmtEditorPageDevice parent;
+
+} NmtPageInfiniband;
+
+typedef struct {
+    NmtEditorPageDeviceClass parent;
+
+} NmtPageInfinibandClass;
+
+GType nmt_page_infiniband_get_type(void);
+
+NmtEditorPage *nmt_page_infiniband_new(NMConnection *conn, NmtDeviceEntry *deventry);
+
+#endif /* NMT_PAGE_INFINIBAND_H */
diff --git a/src/nmtui/nmt-page-ip-tunnel.c b/src/nmtui/nmt-page-ip-tunnel.c
new file mode 100644
index 00000000..da6d12b9
--- /dev/null
+++ b/src/nmtui/nmt-page-ip-tunnel.c
@@ -0,0 +1,203 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2016 Red Hat, Inc.
+ */
+/**
+ * SECTION:nmt-page-ip_tunnel
+ * @short_description: The editor page for IP tunnel connections
+ */
+
+#include "libnm-client-aux-extern/nm-default-client.h"
+
+#include "nmt-page-ip-tunnel.h"
+
+#include "nmt-device-entry.h"
+#include "nmt-mtu-entry.h"
+
+G_DEFINE_TYPE(NmtPageIPTunnel, nmt_page_ip_tunnel, NMT_TYPE_EDITOR_PAGE_DEVICE)
+
+#define NMT_PAGE_IP_TUNNEL_GET_PRIVATE(o) \
+    (G_TYPE_INSTANCE_GET_PRIVATE((o), NMT_TYPE_PAGE_IP_TUNNEL, NmtPageIPTunnelPrivate))
+
+typedef struct {
+    NmtNewtEntry *input_key;
+    NmtNewtEntry *output_key;
+} NmtPageIPTunnelPrivate;
+
+NmtEditorPage *
+nmt_page_ip_tunnel_new(NMConnection *conn, NmtDeviceEntry *deventry)
+{
+    return g_object_new(NMT_TYPE_PAGE_IP_TUNNEL,
+                        "connection",
+                        conn,
+                        "device-entry",
+                        deventry,
+                        NULL);
+}
+
+static void
+nmt_page_ip_tunnel_init(NmtPageIPTunnel *ip_tunnel)
+{}
+
+static void
+mode_changed(GObject *object, GParamSpec *pspec, gpointer user_data)
+{
+    NMSettingIPTunnel *     s_ip_tunnel = NM_SETTING_IP_TUNNEL(object);
+    NmtPageIPTunnel *       ip_tunnel   = NMT_PAGE_IP_TUNNEL(user_data);
+    NmtPageIPTunnelPrivate *priv        = NMT_PAGE_IP_TUNNEL_GET_PRIVATE(ip_tunnel);
+    NMIPTunnelMode          mode;
+    gboolean                enable_keys;
+
+    mode        = nm_setting_ip_tunnel_get_mode(s_ip_tunnel);
+    enable_keys = NM_IN_SET(mode, NM_IP_TUNNEL_MODE_GRE, NM_IP_TUNNEL_MODE_IP6GRE);
+    nmt_newt_widget_set_visible(NMT_NEWT_WIDGET(priv->input_key), enable_keys);
+    nmt_newt_widget_set_visible(NMT_NEWT_WIDGET(priv->output_key), enable_keys);
+
+    if (!enable_keys) {
+        nmt_newt_entry_set_text(priv->input_key, "");
+        nmt_newt_entry_set_text(priv->output_key, "");
+    }
+}
+
+static NmtNewtPopupEntry tunnel_mode[] = {
+    /* The order must match the NM_IP_TUNNEL_MODE_* enum */
+    {N_("IPIP"), "IPIP"},
+    {N_("GRE"), "GRE"},
+    {N_("SIT"), "SIT"},
+    {N_("ISATAP"), "ISATAP"},
+    {N_("VTI"), "VTI"},
+    {N_("IP6IP6"), "IP6IP6"},
+    {N_("IPIP6"), "IPIP6"},
+    {N_("IP6GRE"), "IP6GRE"},
+    {N_("VTI6"), "VTI6"},
+    {NULL, NULL}};
+
+static gboolean
+add_offset(GBinding *binding, const GValue *from_value, GValue *to_value, gpointer user_data)
+{
+    guint v;
+    int   offset = GPOINTER_TO_INT(user_data);
+
+    g_return_val_if_fail(G_VALUE_HOLDS(from_value, G_TYPE_UINT), FALSE);
+    g_return_val_if_fail(G_VALUE_HOLDS(to_value, G_TYPE_UINT), FALSE);
+
+    v = g_value_get_uint(from_value);
+    v += offset;
+
+    g_value_set_uint(to_value, v);
+
+    return TRUE;
+}
+
+static void
+nmt_page_ip_tunnel_constructed(GObject *object)
+{
+    NmtPageIPTunnel *       ip_tunnel = NMT_PAGE_IP_TUNNEL(object);
+    NmtPageIPTunnelPrivate *priv      = NMT_PAGE_IP_TUNNEL_GET_PRIVATE(ip_tunnel);
+    NmtEditorSection *      section;
+    NmtEditorGrid *         grid;
+    NMSettingIPTunnel *     s_ip_tunnel;
+    NmtNewtWidget *         widget, *parent;
+    NMConnection *          conn;
+    GClosure *              s2w, *w2s;
+
+    conn        = nmt_editor_page_get_connection(NMT_EDITOR_PAGE(ip_tunnel));
+    s_ip_tunnel = nm_connection_get_setting_ip_tunnel(conn);
+    if (!s_ip_tunnel) {
+        nm_connection_add_setting(conn, nm_setting_ip_tunnel_new());
+        s_ip_tunnel = nm_connection_get_setting_ip_tunnel(conn);
+    }
+
+    /* Initialize the mode for new connections */
+    if (nm_setting_ip_tunnel_get_mode(s_ip_tunnel) == NM_IP_TUNNEL_MODE_UNKNOWN) {
+        g_object_set(s_ip_tunnel, NM_SETTING_IP_TUNNEL_MODE, (guint) NM_IP_TUNNEL_MODE_IPIP, NULL);
+    }
+
+    section = nmt_editor_section_new(_("IP tunnel"), NULL, TRUE);
+    grid    = nmt_editor_section_get_body(section);
+
+    /* To convert between widget index (0-based) and setting index (1-based) */
+    s2w = g_cclosure_new(G_CALLBACK(add_offset), GINT_TO_POINTER(-1), NULL);
+    w2s = g_cclosure_new(G_CALLBACK(add_offset), GINT_TO_POINTER(1), NULL);
+
+    widget = nmt_newt_popup_new(tunnel_mode);
+    g_object_bind_property_with_closures(s_ip_tunnel,
+                                         NM_SETTING_IP_TUNNEL_MODE,
+                                         widget,
+                                         "active",
+                                         G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE,
+                                         s2w,
+                                         w2s);
+    nmt_editor_grid_append(grid, _("Mode"), widget, NULL);
+
+    widget = parent = nmt_device_entry_new(_("Parent"), 40, G_TYPE_NONE);
+    g_object_bind_property(s_ip_tunnel,
+                           NM_SETTING_IP_TUNNEL_PARENT,
+                           widget,
+                           "interface-name",
+                           G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+    nmt_editor_grid_append(grid, NULL, widget, NULL);
+
+    widget = nmt_newt_entry_new(40, 0);
+    nmt_editor_grid_append(grid, _("Local IP"), widget, NULL);
+    g_object_bind_property(s_ip_tunnel,
+                           NM_SETTING_IP_TUNNEL_LOCAL,
+                           widget,
+                           "text",
+                           G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
+
+    widget = nmt_newt_entry_new(40, 0);
+    nmt_editor_grid_append(grid, _("Remote IP"), widget, NULL);
+    g_object_bind_property(s_ip_tunnel,
+                           NM_SETTING_IP_TUNNEL_REMOTE,
+                           widget,
+                           "text",
+                           G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
+
+    widget = nmt_newt_entry_new(40, 0);
+    nmt_editor_grid_append(grid, _("Input key"), widget, NULL);
+    g_object_bind_property(s_ip_tunnel,
+                           NM_SETTING_IP_TUNNEL_INPUT_KEY,
+                           widget,
+                           "text",
+                           G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
+    priv->input_key = NMT_NEWT_ENTRY(widget);
+
+    widget = nmt_newt_entry_new(40, 0);
+    nmt_editor_grid_append(grid, _("Output key"), widget, NULL);
+    g_object_bind_property(s_ip_tunnel,
+                           NM_SETTING_IP_TUNNEL_OUTPUT_KEY,
+                           widget,
+                           "text",
+                           G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
+    priv->output_key = NMT_NEWT_ENTRY(widget);
+
+    widget = nmt_mtu_entry_new();
+    g_object_bind_property(s_ip_tunnel,
+                           NM_SETTING_IP_TUNNEL_MTU,
+                           widget,
+                           "mtu",
+                           G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+    nmt_editor_grid_append(grid, _("MTU"), widget, NULL);
+
+    g_signal_connect(s_ip_tunnel,
+                     "notify::" NM_SETTING_IP_TUNNEL_MODE,
+                     G_CALLBACK(mode_changed),
+                     ip_tunnel);
+    mode_changed(G_OBJECT(s_ip_tunnel), NULL, ip_tunnel);
+
+    nmt_editor_page_add_section(NMT_EDITOR_PAGE(ip_tunnel), section);
+
+    G_OBJECT_CLASS(nmt_page_ip_tunnel_parent_class)->constructed(object);
+}
+
+static void
+nmt_page_ip_tunnel_class_init(NmtPageIPTunnelClass *ip_tunnel_class)
+{
+    GObjectClass *object_class = G_OBJECT_CLASS(ip_tunnel_class);
+
+    g_type_class_add_private(ip_tunnel_class, sizeof(NmtPageIPTunnelPrivate));
+
+    /* virtual methods */
+    object_class->constructed = nmt_page_ip_tunnel_constructed;
+}
diff --git a/src/nmtui/nmt-page-ip-tunnel.h b/src/nmtui/nmt-page-ip-tunnel.h
new file mode 100644
index 00000000..dcc27880
--- /dev/null
+++ b/src/nmtui/nmt-page-ip-tunnel.h
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2016 Red Hat, Inc.
+ */
+
+#ifndef NMT_PAGE_IP_TUNNEL_H
+#define NMT_PAGE_IP_TUNNEL_H
+
+#include "nmt-editor-page-device.h"
+
+#define NMT_TYPE_PAGE_IP_TUNNEL (nmt_page_ip_tunnel_get_type())
+#define NMT_PAGE_IP_TUNNEL(obj) \
+    (G_TYPE_CHECK_INSTANCE_CAST((obj), NMT_TYPE_PAGE_IP_TUNNEL, NmtPageIPTunnel))
+#define NMT_PAGE_IP_TUNNEL_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NMT_TYPE_PAGE_IP_TUNNEL, NmtPageIPTunnelClass))
+#define NMT_IS_PAGE_IP_TUNNEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMT_TYPE_PAGE_IP_TUNNEL))
+#define NMT_IS_PAGE_IP_TUNNEL_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_TYPE((klass), NMT_TYPE_PAGE_IP_TUNNEL))
+#define NMT_PAGE_IP_TUNNEL_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NMT_TYPE_PAGE_IP_TUNNEL, NmtPageIPTunnelClass))
+
+typedef struct {
+    NmtEditorPageDevice parent;
+} NmtPageIPTunnel;
+
+typedef struct {
+    NmtEditorPageDeviceClass parent;
+} NmtPageIPTunnelClass;
+
+GType nmt_page_ip_tunnel_get_type(void);
+
+NmtEditorPage *nmt_page_ip_tunnel_new(NMConnection *conn, NmtDeviceEntry *deventry);
+
+#endif /* NMT_PAGE_IP_TUNNEL_H */
diff --git a/src/nmtui/nmt-page-ip4.c b/src/nmtui/nmt-page-ip4.c
new file mode 100644
index 00000000..ecd71447
--- /dev/null
+++ b/src/nmtui/nmt-page-ip4.c
@@ -0,0 +1,212 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+/**
+ * SECTION:nmt-page-ip4
+ * @short_description: The editor page for IP4 configuration
+ */
+
+#include "libnm-client-aux-extern/nm-default-client.h"
+
+#include <stdlib.h>
+
+#include "nmt-page-ip4.h"
+#include "nmt-ip-entry.h"
+#include "nmt-address-list.h"
+#include "nmt-route-editor.h"
+
+#include "nm-editor-bindings.h"
+
+G_DEFINE_TYPE(NmtPageIP4, nmt_page_ip4, NMT_TYPE_EDITOR_PAGE)
+
+static NmtNewtPopupEntry ip4methods[] = {
+    {N_("Disabled"), NM_SETTING_IP4_CONFIG_METHOD_DISABLED},
+    {N_("Automatic"), NM_SETTING_IP4_CONFIG_METHOD_AUTO},
+    {N_("Link-Local"), NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL},
+    {N_("Manual"), NM_SETTING_IP4_CONFIG_METHOD_MANUAL},
+    {N_("Shared"), NM_SETTING_IP4_CONFIG_METHOD_SHARED},
+    {NULL, NULL}};
+
+NmtEditorPage *
+nmt_page_ip4_new(NMConnection *conn)
+{
+    return g_object_new(NMT_TYPE_PAGE_IP4, "connection", conn, NULL);
+}
+
+static void
+nmt_page_ip4_init(NmtPageIP4 *ip4)
+{}
+
+static void
+edit_routes(NmtNewtButton *button, gpointer user_data)
+{
+    NMSetting *  s_ip4 = user_data;
+    NmtNewtForm *form;
+
+    form = nmt_route_editor_new(s_ip4);
+    nmt_newt_form_run_sync(form);
+    g_object_unref(form);
+}
+
+static gboolean
+ip4_routes_transform_to_description(GBinding *    binding,
+                                    const GValue *source_value,
+                                    GValue *      target_value,
+                                    gpointer      user_data)
+{
+    GPtrArray *routes;
+    char *     text;
+
+    routes = g_value_get_boxed(source_value);
+    if (!routes || !routes->len)
+        text = g_strdup(_("(No custom routes)"));
+    else {
+        text = g_strdup_printf(
+            g_dngettext(GETTEXT_PACKAGE, "One custom route", "%d custom routes", routes->len),
+            routes->len);
+    }
+
+    g_value_take_string(target_value, text);
+    return TRUE;
+}
+
+static void
+nmt_page_ip4_constructed(GObject *object)
+{
+    NmtPageIP4 *       ip4 = NMT_PAGE_IP4(object);
+    gboolean           show_by_default;
+    NmtEditorSection * section;
+    NmtEditorGrid *    grid;
+    NMSettingIPConfig *s_ip4;
+    NmtNewtWidget *    widget, *button;
+    NMConnection *     conn;
+
+    conn  = nmt_editor_page_get_connection(NMT_EDITOR_PAGE(ip4));
+    s_ip4 = nm_connection_get_setting_ip4_config(conn);
+    if (!s_ip4) {
+        s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
+        g_object_set(G_OBJECT(s_ip4),
+                     NM_SETTING_IP_CONFIG_METHOD,
+                     NM_SETTING_IP4_CONFIG_METHOD_AUTO,
+                     NULL);
+        nm_connection_add_setting(conn, (NMSetting *) s_ip4);
+    }
+
+    widget = nmt_newt_popup_new(ip4methods);
+    g_object_bind_property(s_ip4,
+                           NM_SETTING_IP_CONFIG_METHOD,
+                           widget,
+                           "active-id",
+                           G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+
+    if (!g_strcmp0(nm_setting_ip_config_get_method(s_ip4), NM_SETTING_IP4_CONFIG_METHOD_MANUAL))
+        show_by_default = TRUE;
+    else if (nm_setting_ip_config_get_num_addresses(s_ip4))
+        show_by_default = TRUE;
+    else
+        show_by_default = FALSE;
+
+    section = nmt_editor_section_new(_("IPv4 CONFIGURATION"), widget, show_by_default);
+    grid    = nmt_editor_section_get_body(section);
+
+    widget = nmt_address_list_new(NMT_ADDRESS_LIST_IP4_WITH_PREFIX);
+    nm_editor_bind_ip_addresses_with_prefix_to_strv(AF_INET,
+                                                    s_ip4,
+                                                    NM_SETTING_IP_CONFIG_ADDRESSES,
+                                                    widget,
+                                                    "strings",
+                                                    G_BINDING_BIDIRECTIONAL
+                                                        | G_BINDING_SYNC_CREATE);
+    nmt_editor_grid_append(grid, _("Addresses"), widget, NULL);
+
+    widget = nmt_ip_entry_new(25, AF_INET, FALSE, TRUE);
+    nm_editor_bind_ip_gateway_to_string(AF_INET,
+                                        s_ip4,
+                                        widget,
+                                        "text",
+                                        "sensitive",
+                                        G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+    nmt_editor_grid_append(grid, _("Gateway"), widget, NULL);
+
+    widget = nmt_address_list_new(NMT_ADDRESS_LIST_IP4);
+    nm_editor_bind_ip_addresses_to_strv(AF_INET,
+                                        s_ip4,
+                                        NM_SETTING_IP_CONFIG_DNS,
+                                        widget,
+                                        "strings",
+                                        G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+    nmt_editor_grid_append(grid, _("DNS servers"), widget, NULL);
+
+    widget = nmt_address_list_new(NMT_ADDRESS_LIST_HOSTNAME);
+    g_object_bind_property(s_ip4,
+                           NM_SETTING_IP_CONFIG_DNS_SEARCH,
+                           widget,
+                           "strings",
+                           G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+    nmt_editor_grid_append(grid, _("Search domains"), widget, NULL);
+
+    nmt_editor_grid_append(grid, NULL, nmt_newt_separator_new(), NULL);
+
+    widget = g_object_new(NMT_TYPE_NEWT_LABEL, "text", "", "style", NMT_NEWT_LABEL_PLAIN, NULL);
+    g_object_bind_property_full(s_ip4,
+                                NM_SETTING_IP_CONFIG_ROUTES,
+                                widget,
+                                "text",
+                                G_BINDING_SYNC_CREATE,
+                                ip4_routes_transform_to_description,
+                                NULL,
+                                NULL,
+                                NULL);
+    button = nmt_newt_button_new(_("Edit..."));
+    g_signal_connect(button, "clicked", G_CALLBACK(edit_routes), s_ip4);
+    nmt_editor_grid_append(grid, _("Routing"), widget, button);
+
+    widget = nmt_newt_checkbox_new(_("Never use this network for default route"));
+    g_object_bind_property(s_ip4,
+                           NM_SETTING_IP_CONFIG_NEVER_DEFAULT,
+                           widget,
+                           "active",
+                           G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
+    nmt_editor_grid_append(grid, NULL, widget, NULL);
+
+    widget = nmt_newt_checkbox_new(_("Ignore automatically obtained routes"));
+    g_object_bind_property(s_ip4,
+                           NM_SETTING_IP_CONFIG_IGNORE_AUTO_ROUTES,
+                           widget,
+                           "active",
+                           G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
+    nmt_editor_grid_append(grid, NULL, widget, NULL);
+
+    widget = nmt_newt_checkbox_new(_("Ignore automatically obtained DNS parameters"));
+    g_object_bind_property(s_ip4,
+                           NM_SETTING_IP_CONFIG_IGNORE_AUTO_DNS,
+                           widget,
+                           "active",
+                           G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
+    nmt_editor_grid_append(grid, NULL, widget, NULL);
+
+    nmt_editor_grid_append(grid, NULL, nmt_newt_separator_new(), NULL);
+
+    widget = nmt_newt_checkbox_new(_("Require IPv4 addressing for this connection"));
+    g_object_bind_property(s_ip4,
+                           NM_SETTING_IP_CONFIG_MAY_FAIL,
+                           widget,
+                           "active",
+                           G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL
+                               | G_BINDING_INVERT_BOOLEAN);
+    nmt_editor_grid_append(grid, NULL, widget, NULL);
+
+    nmt_editor_page_add_section(NMT_EDITOR_PAGE(ip4), section);
+
+    G_OBJECT_CLASS(nmt_page_ip4_parent_class)->constructed(object);
+}
+
+static void
+nmt_page_ip4_class_init(NmtPageIP4Class *ip4_class)
+{
+    GObjectClass *object_class = G_OBJECT_CLASS(ip4_class);
+
+    object_class->constructed = nmt_page_ip4_constructed;
+}
diff --git a/src/nmtui/nmt-page-ip4.h b/src/nmtui/nmt-page-ip4.h
new file mode 100644
index 00000000..52d3c842
--- /dev/null
+++ b/src/nmtui/nmt-page-ip4.h
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+#ifndef NMT_PAGE_IP4_H
+#define NMT_PAGE_IP4_H
+
+#include "nmt-editor-page.h"
+
+#define NMT_TYPE_PAGE_IP4 (nmt_page_ip4_get_type())
+#define NMT_PAGE_IP4(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NMT_TYPE_PAGE_IP4, NmtPageIP4))
+#define NMT_PAGE_IP4_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NMT_TYPE_PAGE_IP4, NmtPageIP4Class))
+#define NMT_IS_PAGE_IP4(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMT_TYPE_PAGE_IP4))
+#define NMT_IS_PAGE_IP4_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NMT_TYPE_PAGE_IP4))
+#define NMT_PAGE_IP4_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NMT_TYPE_PAGE_IP4, NmtPageIP4Class))
+
+typedef struct {
+    NmtEditorPage parent;
+
+} NmtPageIP4;
+
+typedef struct {
+    NmtEditorPageClass parent;
+
+} NmtPageIP4Class;
+
+GType nmt_page_ip4_get_type(void);
+
+NmtEditorPage *nmt_page_ip4_new(NMConnection *conn);
+
+#endif /* NMT_PAGE_IP4_H */
diff --git a/src/nmtui/nmt-page-ip6.c b/src/nmtui/nmt-page-ip6.c
new file mode 100644
index 00000000..f79c553d
--- /dev/null
+++ b/src/nmtui/nmt-page-ip6.c
@@ -0,0 +1,211 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+/**
+ * SECTION:nmt-page-ip6
+ * @short_description: The editor page for IP6 configuration
+ */
+
+#include "libnm-client-aux-extern/nm-default-client.h"
+
+#include <stdlib.h>
+
+#include "nmt-page-ip6.h"
+#include "nmt-ip-entry.h"
+#include "nmt-address-list.h"
+#include "nmt-route-editor.h"
+
+#include "nm-editor-bindings.h"
+
+G_DEFINE_TYPE(NmtPageIP6, nmt_page_ip6, NMT_TYPE_EDITOR_PAGE)
+
+static NmtNewtPopupEntry ip6methods[] = {
+    {N_("Ignore"), NM_SETTING_IP6_CONFIG_METHOD_IGNORE},
+    {N_("Automatic"), NM_SETTING_IP6_CONFIG_METHOD_AUTO},
+    {N_("Automatic (DHCP-only)"), NM_SETTING_IP6_CONFIG_METHOD_DHCP},
+    {N_("Link-Local"), NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL},
+    {N_("Manual"), NM_SETTING_IP6_CONFIG_METHOD_MANUAL},
+    {N_("Disabled"), NM_SETTING_IP6_CONFIG_METHOD_DISABLED},
+    {NULL, NULL}};
+
+NmtEditorPage *
+nmt_page_ip6_new(NMConnection *conn)
+{
+    return g_object_new(NMT_TYPE_PAGE_IP6, "connection", conn, NULL);
+}
+
+static void
+nmt_page_ip6_init(NmtPageIP6 *ip6)
+{}
+
+static void
+edit_routes(NmtNewtButton *button, gpointer user_data)
+{
+    NMSetting *  s_ip6 = user_data;
+    NmtNewtForm *form;
+
+    form = nmt_route_editor_new(s_ip6);
+    nmt_newt_form_run_sync(form);
+    g_object_unref(form);
+}
+
+static gboolean
+ip6_routes_transform_to_description(GBinding *    binding,
+                                    const GValue *source_value,
+                                    GValue *      target_value,
+                                    gpointer      user_data)
+{
+    GPtrArray *routes;
+    char *     text;
+
+    routes = g_value_get_boxed(source_value);
+    if (!routes || !routes->len)
+        text = g_strdup(_("(No custom routes)"));
+    else {
+        text = g_strdup_printf(
+            g_dngettext(GETTEXT_PACKAGE, "One custom route", "%d custom routes", routes->len),
+            routes->len);
+    }
+
+    g_value_take_string(target_value, text);
+    return TRUE;
+}
+
+static void
+nmt_page_ip6_constructed(GObject *object)
+{
+    NmtPageIP6 *       ip6 = NMT_PAGE_IP6(object);
+    gboolean           show_by_default;
+    NmtEditorSection * section;
+    NmtEditorGrid *    grid;
+    NMSettingIPConfig *s_ip6;
+    NmtNewtWidget *    widget, *button;
+    NMConnection *     conn;
+
+    conn  = nmt_editor_page_get_connection(NMT_EDITOR_PAGE(ip6));
+    s_ip6 = nm_connection_get_setting_ip6_config(conn);
+    if (!s_ip6) {
+        s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
+        g_object_set(G_OBJECT(s_ip6),
+                     NM_SETTING_IP_CONFIG_METHOD,
+                     NM_SETTING_IP6_CONFIG_METHOD_AUTO,
+                     NULL);
+        nm_connection_add_setting(conn, (NMSetting *) s_ip6);
+    }
+
+    widget = nmt_newt_popup_new(ip6methods);
+    g_object_bind_property(s_ip6,
+                           NM_SETTING_IP_CONFIG_METHOD,
+                           widget,
+                           "active-id",
+                           G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+
+    if (!g_strcmp0(nm_setting_ip_config_get_method(s_ip6), NM_SETTING_IP6_CONFIG_METHOD_MANUAL))
+        show_by_default = TRUE;
+    else if (nm_setting_ip_config_get_num_addresses(s_ip6))
+        show_by_default = TRUE;
+    else
+        show_by_default = FALSE;
+
+    section = nmt_editor_section_new(_("IPv6 CONFIGURATION"), widget, show_by_default);
+    grid    = nmt_editor_section_get_body(section);
+
+    widget = nmt_address_list_new(NMT_ADDRESS_LIST_IP6_WITH_PREFIX);
+    nm_editor_bind_ip_addresses_with_prefix_to_strv(AF_INET6,
+                                                    s_ip6,
+                                                    NM_SETTING_IP_CONFIG_ADDRESSES,
+                                                    widget,
+                                                    "strings",
+                                                    G_BINDING_BIDIRECTIONAL
+                                                        | G_BINDING_SYNC_CREATE);
+    nmt_editor_grid_append(grid, _("Addresses"), widget, NULL);
+
+    widget = nmt_ip_entry_new(25, AF_INET6, FALSE, TRUE);
+    nm_editor_bind_ip_gateway_to_string(AF_INET6,
+                                        s_ip6,
+                                        widget,
+                                        "text",
+                                        "sensitive",
+                                        G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+    nmt_editor_grid_append(grid, _("Gateway"), widget, NULL);
+
+    widget = nmt_address_list_new(NMT_ADDRESS_LIST_IP6);
+    nm_editor_bind_ip_addresses_to_strv(AF_INET6,
+                                        s_ip6,
+                                        NM_SETTING_IP_CONFIG_DNS,
+                                        widget,
+                                        "strings",
+                                        G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+    nmt_editor_grid_append(grid, _("DNS servers"), widget, NULL);
+
+    widget = nmt_address_list_new(NMT_ADDRESS_LIST_HOSTNAME);
+    g_object_bind_property(s_ip6,
+                           NM_SETTING_IP_CONFIG_DNS_SEARCH,
+                           widget,
+                           "strings",
+                           G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+    nmt_editor_grid_append(grid, _("Search domains"), widget, NULL);
+
+    widget = g_object_new(NMT_TYPE_NEWT_LABEL, "text", "", "style", NMT_NEWT_LABEL_PLAIN, NULL);
+    g_object_bind_property_full(s_ip6,
+                                NM_SETTING_IP_CONFIG_ROUTES,
+                                widget,
+                                "text",
+                                G_BINDING_SYNC_CREATE,
+                                ip6_routes_transform_to_description,
+                                NULL,
+                                NULL,
+                                NULL);
+    button = nmt_newt_button_new(_("Edit..."));
+    g_signal_connect(button, "clicked", G_CALLBACK(edit_routes), s_ip6);
+    nmt_editor_grid_append(grid, _("Routing"), widget, button);
+
+    widget = nmt_newt_checkbox_new(_("Never use this network for default route"));
+    g_object_bind_property(s_ip6,
+                           NM_SETTING_IP_CONFIG_NEVER_DEFAULT,
+                           widget,
+                           "active",
+                           G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
+    nmt_editor_grid_append(grid, NULL, widget, NULL);
+
+    widget = nmt_newt_checkbox_new(_("Ignore automatically obtained routes"));
+    g_object_bind_property(s_ip6,
+                           NM_SETTING_IP_CONFIG_IGNORE_AUTO_ROUTES,
+                           widget,
+                           "active",
+                           G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
+    nmt_editor_grid_append(grid, NULL, widget, NULL);
+
+    widget = nmt_newt_checkbox_new(_("Ignore automatically obtained DNS parameters"));
+    g_object_bind_property(s_ip6,
+                           NM_SETTING_IP_CONFIG_IGNORE_AUTO_DNS,
+                           widget,
+                           "active",
+                           G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
+    nmt_editor_grid_append(grid, NULL, widget, NULL);
+
+    nmt_editor_grid_append(grid, NULL, nmt_newt_separator_new(), NULL);
+
+    widget = nmt_newt_checkbox_new(_("Require IPv6 addressing for this connection"));
+    g_object_bind_property(s_ip6,
+                           NM_SETTING_IP_CONFIG_MAY_FAIL,
+                           widget,
+                           "active",
+                           G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL
+                               | G_BINDING_INVERT_BOOLEAN);
+    nmt_editor_grid_append(grid, NULL, widget, NULL);
+
+    nmt_editor_page_add_section(NMT_EDITOR_PAGE(ip6), section);
+
+    G_OBJECT_CLASS(nmt_page_ip6_parent_class)->constructed(object);
+}
+
+static void
+nmt_page_ip6_class_init(NmtPageIP6Class *ip6_class)
+{
+    GObjectClass *object_class = G_OBJECT_CLASS(ip6_class);
+
+    object_class->constructed = nmt_page_ip6_constructed;
+}
diff --git a/src/nmtui/nmt-page-ip6.h b/src/nmtui/nmt-page-ip6.h
new file mode 100644
index 00000000..cefb36b9
--- /dev/null
+++ b/src/nmtui/nmt-page-ip6.h
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+#ifndef NMT_PAGE_IP6_H
+#define NMT_PAGE_IP6_H
+
+#include "nmt-editor-page.h"
+
+#define NMT_TYPE_PAGE_IP6 (nmt_page_ip6_get_type())
+#define NMT_PAGE_IP6(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NMT_TYPE_PAGE_IP6, NmtPageIP6))
+#define NMT_PAGE_IP6_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NMT_TYPE_PAGE_IP6, NmtPageIP6Class))
+#define NMT_IS_PAGE_IP6(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMT_TYPE_PAGE_IP6))
+#define NMT_IS_PAGE_IP6_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NMT_TYPE_PAGE_IP6))
+#define NMT_PAGE_IP6_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NMT_TYPE_PAGE_IP6, NmtPageIP6Class))
+
+typedef struct {
+    NmtEditorPage parent;
+
+} NmtPageIP6;
+
+typedef struct {
+    NmtEditorPageClass parent;
+
+} NmtPageIP6Class;
+
+GType nmt_page_ip6_get_type(void);
+
+NmtEditorPage *nmt_page_ip6_new(NMConnection *conn);
+
+#endif /* NMT_PAGE_IP6_H */
diff --git a/src/nmtui/nmt-page-ppp.c b/src/nmtui/nmt-page-ppp.c
new file mode 100644
index 00000000..ffa1a8b2
--- /dev/null
+++ b/src/nmtui/nmt-page-ppp.c
@@ -0,0 +1,281 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2014 Red Hat, Inc.
+ */
+
+/**
+ * SECTION:nmt-page-ppp
+ * @short_description: The editor page for PPP configuration
+ */
+
+#include "libnm-client-aux-extern/nm-default-client.h"
+
+#include <stdlib.h>
+
+#include "nmt-page-ppp.h"
+#include "libnmt-newt/nmt-newt-section.h"
+#include "libnmt-newt/nmt-newt-separator.h"
+
+G_DEFINE_TYPE(NmtPagePpp, nmt_page_ppp, NMT_TYPE_EDITOR_PAGE)
+
+typedef struct {
+    guint32 lcp_echo_failure;
+    guint32 lcp_echo_interval;
+} NmtPagePppPrivate;
+
+#define NMT_PAGE_PPP_GET_PRIVATE(o) \
+    (G_TYPE_INSTANCE_GET_PRIVATE((o), NMT_TYPE_PAGE_PPP, NmtPagePppPrivate))
+
+NmtEditorPage *
+nmt_page_ppp_new(NMConnection *conn)
+{
+    return g_object_new(NMT_TYPE_PAGE_PPP, "connection", conn, NULL);
+}
+
+static void
+nmt_page_ppp_init(NmtPagePpp *ppp)
+{}
+
+static gboolean
+transform_lcp_echo_properties_to_checkbox(GBinding *    binding,
+                                          const GValue *from_value,
+                                          GValue *      to_value,
+                                          gpointer      user_data)
+{
+    NMSettingPpp *s_ppp = NM_SETTING_PPP(g_binding_get_source(binding));
+
+    if (nm_setting_ppp_get_lcp_echo_interval(s_ppp) != 0
+        && nm_setting_ppp_get_lcp_echo_failure(s_ppp) != 0)
+        g_value_set_boolean(to_value, TRUE);
+    else
+        g_value_set_boolean(to_value, FALSE);
+
+    return TRUE;
+}
+
+static gboolean
+transform_checkbox_to_lcp_echo_interval(GBinding *    binding,
+                                        const GValue *from_value,
+                                        GValue *      to_value,
+                                        gpointer      user_data)
+{
+    NmtPagePpp *       ppp  = user_data;
+    NmtPagePppPrivate *priv = NMT_PAGE_PPP_GET_PRIVATE(ppp);
+
+    if (g_value_get_boolean(from_value))
+        g_value_set_uint(to_value, priv->lcp_echo_interval);
+    else
+        g_value_set_uint(to_value, 0);
+
+    return TRUE;
+}
+
+static gboolean
+transform_checkbox_to_lcp_echo_failure(GBinding *    binding,
+                                       const GValue *from_value,
+                                       GValue *      to_value,
+                                       gpointer      user_data)
+{
+    NmtPagePpp *       ppp  = user_data;
+    NmtPagePppPrivate *priv = NMT_PAGE_PPP_GET_PRIVATE(ppp);
+
+    if (g_value_get_boolean(from_value))
+        g_value_set_uint(to_value, priv->lcp_echo_failure);
+    else
+        g_value_set_uint(to_value, 0);
+
+    return TRUE;
+}
+
+static void
+nmt_page_ppp_constructed(GObject *object)
+{
+    NmtPagePpp *       ppp  = NMT_PAGE_PPP(object);
+    NmtPagePppPrivate *priv = NMT_PAGE_PPP_GET_PRIVATE(ppp);
+    NmtEditorSection * section;
+    NmtEditorGrid *    grid;
+    NMSettingPpp *     s_ppp;
+    NmtNewtWidget *    widget, *use_mppe;
+    NmtNewtGrid *      auth_grid, *mppe_grid;
+    NmtNewtSection *   auth_section, *mppe_section;
+    NMConnection *     conn;
+
+    conn  = nmt_editor_page_get_connection(NMT_EDITOR_PAGE(ppp));
+    s_ppp = nm_connection_get_setting_ppp(conn);
+    if (s_ppp) {
+        priv->lcp_echo_interval = nm_setting_ppp_get_lcp_echo_interval(s_ppp);
+        priv->lcp_echo_failure  = nm_setting_ppp_get_lcp_echo_failure(s_ppp);
+    } else {
+        s_ppp = (NMSettingPpp *) nm_setting_ppp_new();
+        nm_connection_add_setting(conn, (NMSetting *) s_ppp);
+
+        priv->lcp_echo_interval = 30;
+        priv->lcp_echo_failure  = 5;
+    }
+
+    section = nmt_editor_section_new(_("PPP CONFIGURATION"), NULL, TRUE);
+    grid    = nmt_editor_section_get_body(section);
+
+    /* Auth methods */
+    widget       = nmt_newt_section_new(FALSE);
+    auth_section = NMT_NEWT_SECTION(widget);
+    g_object_set(auth_section, "open", TRUE, NULL);
+    nmt_editor_grid_append(grid, NULL, widget, NULL);
+
+    widget = nmt_newt_label_new(_("Allowed authentication methods:"));
+    nmt_newt_section_set_header(auth_section, widget);
+
+    widget    = nmt_newt_grid_new();
+    auth_grid = NMT_NEWT_GRID(widget);
+    nmt_newt_section_set_body(auth_section, widget);
+
+    widget = nmt_newt_checkbox_new(_("EAP"));
+    g_object_bind_property(s_ppp,
+                           NM_SETTING_PPP_REFUSE_EAP,
+                           widget,
+                           "active",
+                           G_BINDING_BIDIRECTIONAL | G_BINDING_INVERT_BOOLEAN
+                               | G_BINDING_SYNC_CREATE);
+    nmt_newt_grid_add(auth_grid, widget, 0, 0);
+
+    widget = nmt_newt_checkbox_new(_("PAP"));
+    g_object_bind_property(s_ppp,
+                           NM_SETTING_PPP_REFUSE_PAP,
+                           widget,
+                           "active",
+                           G_BINDING_BIDIRECTIONAL | G_BINDING_INVERT_BOOLEAN
+                               | G_BINDING_SYNC_CREATE);
+    nmt_newt_grid_add(auth_grid, widget, 0, 1);
+
+    widget = nmt_newt_checkbox_new(_("CHAP"));
+    g_object_bind_property(s_ppp,
+                           NM_SETTING_PPP_REFUSE_CHAP,
+                           widget,
+                           "active",
+                           G_BINDING_BIDIRECTIONAL | G_BINDING_INVERT_BOOLEAN
+                               | G_BINDING_SYNC_CREATE);
+    nmt_newt_grid_add(auth_grid, widget, 0, 2);
+
+    widget = nmt_newt_checkbox_new(_("MSCHAPv2"));
+    g_object_bind_property(s_ppp,
+                           NM_SETTING_PPP_REFUSE_MSCHAPV2,
+                           widget,
+                           "active",
+                           G_BINDING_BIDIRECTIONAL | G_BINDING_INVERT_BOOLEAN
+                               | G_BINDING_SYNC_CREATE);
+    nmt_newt_grid_add(auth_grid, widget, 0, 3);
+
+    widget = nmt_newt_checkbox_new(_("MSCHAP"));
+    g_object_bind_property(s_ppp,
+                           NM_SETTING_PPP_REFUSE_MSCHAP,
+                           widget,
+                           "active",
+                           G_BINDING_BIDIRECTIONAL | G_BINDING_INVERT_BOOLEAN
+                               | G_BINDING_SYNC_CREATE);
+    nmt_newt_grid_add(auth_grid, widget, 0, 4);
+
+    nmt_editor_grid_append(grid, NULL, nmt_newt_separator_new(), NULL);
+
+    /* MPPE */
+    widget       = nmt_newt_section_new(FALSE);
+    mppe_section = NMT_NEWT_SECTION(widget);
+    g_object_set(mppe_section, "open", TRUE, NULL);
+    nmt_editor_grid_append(grid, NULL, widget, NULL);
+
+    widget = nmt_newt_checkbox_new(_("Use point-to-point encryption (MPPE)"));
+    g_object_bind_property(s_ppp,
+                           NM_SETTING_PPP_REQUIRE_MPPE,
+                           widget,
+                           "active",
+                           G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+    use_mppe = widget;
+    nmt_newt_section_set_header(mppe_section, widget);
+
+    widget    = nmt_newt_grid_new();
+    mppe_grid = NMT_NEWT_GRID(widget);
+    nmt_newt_section_set_body(mppe_section, widget);
+
+    widget = nmt_newt_checkbox_new(_("Require 128-bit encryption"));
+    g_object_bind_property(use_mppe, "active", widget, "sensitive", G_BINDING_SYNC_CREATE);
+    g_object_bind_property(s_ppp,
+                           NM_SETTING_PPP_REQUIRE_MPPE_128,
+                           widget,
+                           "active",
+                           G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+    nmt_newt_grid_add(mppe_grid, widget, 0, 0);
+
+    widget = nmt_newt_checkbox_new(_("Use stateful MPPE"));
+    g_object_bind_property(use_mppe, "active", widget, "sensitive", G_BINDING_SYNC_CREATE);
+    g_object_bind_property(s_ppp,
+                           NM_SETTING_PPP_MPPE_STATEFUL,
+                           widget,
+                           "active",
+                           G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+    nmt_newt_grid_add(mppe_grid, widget, 0, 1);
+
+    nmt_editor_grid_append(grid, NULL, nmt_newt_separator_new(), NULL);
+
+    widget = nmt_newt_checkbox_new(_("Allow BSD data compression"));
+    g_object_bind_property(s_ppp,
+                           NM_SETTING_PPP_NOBSDCOMP,
+                           widget,
+                           "active",
+                           G_BINDING_BIDIRECTIONAL | G_BINDING_INVERT_BOOLEAN
+                               | G_BINDING_SYNC_CREATE);
+    nmt_editor_grid_append(grid, NULL, widget, NULL);
+
+    widget = nmt_newt_checkbox_new(_("Allow Deflate data compression"));
+    g_object_bind_property(s_ppp,
+                           NM_SETTING_PPP_NODEFLATE,
+                           widget,
+                           "active",
+                           G_BINDING_BIDIRECTIONAL | G_BINDING_INVERT_BOOLEAN
+                               | G_BINDING_SYNC_CREATE);
+    nmt_editor_grid_append(grid, NULL, widget, NULL);
+
+    widget = nmt_newt_checkbox_new(_("Use TCP header compression"));
+    g_object_bind_property(s_ppp,
+                           NM_SETTING_PPP_NO_VJ_COMP,
+                           widget,
+                           "active",
+                           G_BINDING_BIDIRECTIONAL | G_BINDING_INVERT_BOOLEAN
+                               | G_BINDING_SYNC_CREATE);
+    nmt_editor_grid_append(grid, NULL, widget, NULL);
+
+    nmt_editor_grid_append(grid, NULL, nmt_newt_separator_new(), NULL);
+
+    widget = nmt_newt_checkbox_new(_("Send PPP echo packets"));
+    g_object_bind_property_full(s_ppp,
+                                NM_SETTING_PPP_LCP_ECHO_INTERVAL,
+                                widget,
+                                "active",
+                                G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE,
+                                transform_lcp_echo_properties_to_checkbox,
+                                transform_checkbox_to_lcp_echo_interval,
+                                ppp,
+                                NULL);
+    g_object_bind_property_full(s_ppp,
+                                NM_SETTING_PPP_LCP_ECHO_FAILURE,
+                                widget,
+                                "active",
+                                G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE,
+                                transform_lcp_echo_properties_to_checkbox,
+                                transform_checkbox_to_lcp_echo_failure,
+                                ppp,
+                                NULL);
+    nmt_editor_grid_append(grid, NULL, widget, NULL);
+
+    nmt_editor_page_add_section(NMT_EDITOR_PAGE(ppp), section);
+
+    G_OBJECT_CLASS(nmt_page_ppp_parent_class)->constructed(object);
+}
+
+static void
+nmt_page_ppp_class_init(NmtPagePppClass *ppp_class)
+{
+    GObjectClass *object_class = G_OBJECT_CLASS(ppp_class);
+
+    g_type_class_add_private(object_class, sizeof(NmtPagePppPrivate));
+
+    object_class->constructed = nmt_page_ppp_constructed;
+}
diff --git a/src/nmtui/nmt-page-ppp.h b/src/nmtui/nmt-page-ppp.h
new file mode 100644
index 00000000..8538c102
--- /dev/null
+++ b/src/nmtui/nmt-page-ppp.h
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2014 Red Hat, Inc.
+ */
+
+#ifndef NMT_PAGE_PPP_H
+#define NMT_PAGE_PPP_H
+
+#include "nmt-editor-page.h"
+
+#define NMT_TYPE_PAGE_PPP (nmt_page_ppp_get_type())
+#define NMT_PAGE_PPP(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NMT_TYPE_PAGE_PPP, NmtPagePpp))
+#define NMT_PAGE_PPP_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NMT_TYPE_PAGE_PPP, NmtPagePppClass))
+#define NMT_IS_PAGE_PPP(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMT_TYPE_PAGE_PPP))
+#define NMT_IS_PAGE_PPP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NMT_TYPE_PAGE_PPP))
+#define NMT_PAGE_PPP_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NMT_TYPE_PAGE_PPP, NmtPagePppClass))
+
+typedef struct {
+    NmtEditorPage parent;
+
+} NmtPagePpp;
+
+typedef struct {
+    NmtEditorPageClass parent;
+
+} NmtPagePppClass;
+
+GType nmt_page_ppp_get_type(void);
+
+NmtEditorPage *nmt_page_ppp_new(NMConnection *conn);
+
+#endif /* NMT_PAGE_PPP_H */
diff --git a/src/nmtui/nmt-page-team-port.c b/src/nmtui/nmt-page-team-port.c
new file mode 100644
index 00000000..13bb84f0
--- /dev/null
+++ b/src/nmtui/nmt-page-team-port.c
@@ -0,0 +1,111 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+/**
+ * SECTION:nmt-page-team-port
+ * @short_description: The editor page for Team ports.
+ */
+
+#include "libnm-client-aux-extern/nm-default-client.h"
+
+#include "nmt-page-team-port.h"
+
+G_DEFINE_TYPE(NmtPageTeamPort, nmt_page_team_port, NMT_TYPE_EDITOR_PAGE)
+
+#define NMT_PAGE_TEAM_PORT_GET_PRIVATE(o) \
+    (G_TYPE_INSTANCE_GET_PRIVATE((o), NMT_TYPE_PAGE_TEAM_PORT, NmtPageTeamPortPrivate))
+
+typedef struct {
+    NMSettingTeamPort *s_port;
+
+} NmtPageTeamPortPrivate;
+
+NmtEditorPage *
+nmt_page_team_port_new(NMConnection *conn)
+{
+    return g_object_new(NMT_TYPE_PAGE_TEAM_PORT, "connection", conn, NULL);
+}
+
+static void
+nmt_page_team_port_init(NmtPageTeamPort *team)
+{}
+
+static void
+edit_clicked(NmtNewtButton *button, gpointer user_data)
+{
+    NmtPageTeamPort *       team = user_data;
+    NmtPageTeamPortPrivate *priv = NMT_PAGE_TEAM_PORT_GET_PRIVATE(team);
+    const char *            config;
+    char *                  new_config;
+
+    config = nm_setting_team_port_get_config(priv->s_port);
+    if (!config)
+        config = "";
+
+    new_config = nmt_newt_edit_string(config);
+
+    if (new_config && !*new_config)
+        nm_clear_g_free(&new_config);
+    g_object_set(G_OBJECT(priv->s_port), NM_SETTING_TEAM_PORT_CONFIG, new_config, NULL);
+    g_free(new_config);
+}
+
+static void
+nmt_page_team_port_constructed(GObject *object)
+{
+    NmtPageTeamPort *       team = NMT_PAGE_TEAM_PORT(object);
+    NmtPageTeamPortPrivate *priv = NMT_PAGE_TEAM_PORT_GET_PRIVATE(team);
+    NmtEditorSection *      section;
+    NmtNewtGrid *           grid;
+    NMSettingTeamPort *     s_port;
+    NmtNewtWidget *         widget;
+    NMConnection *          conn;
+
+    conn   = nmt_editor_page_get_connection(NMT_EDITOR_PAGE(team));
+    s_port = nm_connection_get_setting_team_port(conn);
+    if (!s_port) {
+        nm_connection_add_setting(conn, nm_setting_team_port_new());
+        s_port = nm_connection_get_setting_team_port(conn);
+    }
+    priv->s_port = s_port;
+
+    section = nmt_editor_section_new(_("TEAM PORT"), NULL, TRUE);
+
+    widget = nmt_newt_grid_new();
+    nmt_editor_grid_append(nmt_editor_section_get_body(section), NULL, widget, NULL);
+
+    grid = NMT_NEWT_GRID(widget);
+
+    widget = nmt_newt_label_new(_("JSON configuration"));
+    nmt_newt_grid_add(grid, widget, 0, 2);
+
+    widget =
+        nmt_newt_textbox_new(NMT_NEWT_TEXTBOX_SCROLLABLE | NMT_NEWT_TEXTBOX_SET_BACKGROUND, 60);
+    g_object_bind_property(s_port,
+                           NM_SETTING_TEAM_PORT_CONFIG,
+                           widget,
+                           "text",
+                           G_BINDING_SYNC_CREATE);
+    nmt_newt_grid_add(grid, widget, 0, 3);
+    nmt_newt_widget_set_padding(widget, 2, 0, 2, 1);
+
+    widget = nmt_newt_button_new(_("Edit..."));
+    g_signal_connect(widget, "clicked", G_CALLBACK(edit_clicked), team);
+    nmt_newt_grid_add(grid, widget, 0, 4);
+
+    nmt_editor_page_add_section(NMT_EDITOR_PAGE(team), section);
+
+    G_OBJECT_CLASS(nmt_page_team_port_parent_class)->constructed(object);
+}
+
+static void
+nmt_page_team_port_class_init(NmtPageTeamPortClass *team_class)
+{
+    GObjectClass *object_class = G_OBJECT_CLASS(team_class);
+
+    g_type_class_add_private(team_class, sizeof(NmtPageTeamPortPrivate));
+
+    object_class->constructed = nmt_page_team_port_constructed;
+}
diff --git a/src/nmtui/nmt-page-team-port.h b/src/nmtui/nmt-page-team-port.h
new file mode 100644
index 00000000..de6e744e
--- /dev/null
+++ b/src/nmtui/nmt-page-team-port.h
@@ -0,0 +1,36 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+#ifndef NMT_PAGE_TEAM_PORT_H
+#define NMT_PAGE_TEAM_PORT_H
+
+#include "nmt-editor-page-device.h"
+
+#define NMT_TYPE_PAGE_TEAM_PORT (nmt_page_team_port_get_type())
+#define NMT_PAGE_TEAM_PORT(obj) \
+    (G_TYPE_CHECK_INSTANCE_CAST((obj), NMT_TYPE_PAGE_TEAM_PORT, NmtPageTeamPort))
+#define NMT_PAGE_TEAM_PORT_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NMT_TYPE_PAGE_TEAM_PORT, NmtPageTeamPortClass))
+#define NMT_IS_PAGE_TEAM_PORT(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMT_TYPE_PAGE_TEAM_PORT))
+#define NMT_IS_PAGE_TEAM_PORT_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_TYPE((klass), NMT_TYPE_PAGE_TEAM_PORT))
+#define NMT_PAGE_TEAM_PORT_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NMT_TYPE_PAGE_TEAM_PORT, NmtPageTeamPortClass))
+
+typedef struct {
+    NmtEditorPage parent;
+
+} NmtPageTeamPort;
+
+typedef struct {
+    NmtEditorPageClass parent;
+
+} NmtPageTeamPortClass;
+
+GType nmt_page_team_port_get_type(void);
+
+NmtEditorPage *nmt_page_team_port_new(NMConnection *conn);
+
+#endif /* NMT_PAGE_TEAM_PORT_H */
diff --git a/src/nmtui/nmt-page-team.c b/src/nmtui/nmt-page-team.c
new file mode 100644
index 00000000..3cda1257
--- /dev/null
+++ b/src/nmtui/nmt-page-team.c
@@ -0,0 +1,175 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+/**
+ * SECTION:nmt-page-team
+ * @short_description: The editor page for Team connections
+ */
+
+#include "libnm-client-aux-extern/nm-default-client.h"
+
+#include "nmt-page-team.h"
+
+#include "nmt-slave-list.h"
+
+G_DEFINE_TYPE(NmtPageTeam, nmt_page_team, NMT_TYPE_EDITOR_PAGE_DEVICE)
+
+#define NMT_PAGE_TEAM_GET_PRIVATE(o) \
+    (G_TYPE_INSTANCE_GET_PRIVATE((o), NMT_TYPE_PAGE_TEAM, NmtPageTeamPrivate))
+
+typedef struct {
+    NmtSlaveList *slaves;
+
+    NMSettingTeam *s_team;
+    GType          slave_type;
+
+} NmtPageTeamPrivate;
+
+NmtEditorPage *
+nmt_page_team_new(NMConnection *conn, NmtDeviceEntry *deventry)
+{
+    return g_object_new(NMT_TYPE_PAGE_TEAM, "connection", conn, "device-entry", deventry, NULL);
+}
+
+static void
+nmt_page_team_init(NmtPageTeam *team)
+{
+    NmtPageTeamPrivate *priv = NMT_PAGE_TEAM_GET_PRIVATE(team);
+
+    priv->slave_type = G_TYPE_NONE;
+}
+
+static void
+slaves_changed(GObject *object, GParamSpec *pspec, gpointer user_data)
+{
+    NmtPageTeam *       team = NMT_PAGE_TEAM(user_data);
+    NmtPageTeamPrivate *priv = NMT_PAGE_TEAM_GET_PRIVATE(team);
+    GPtrArray *         slaves;
+
+    g_object_get(object, "connections", &slaves, NULL);
+    if (slaves->len == 0) {
+        priv->slave_type = G_TYPE_NONE;
+    } else if (priv->slave_type == G_TYPE_NONE) {
+        NMConnection *slave = slaves->pdata[0];
+
+        if (nm_connection_is_type(slave, NM_SETTING_INFINIBAND_SETTING_NAME))
+            priv->slave_type = NM_TYPE_SETTING_INFINIBAND;
+        else
+            priv->slave_type = NM_TYPE_SETTING_WIRED;
+    }
+}
+
+static gboolean
+team_connection_type_filter(GType connection_type, gpointer user_data)
+{
+    NmtPageTeam *       team = user_data;
+    NmtPageTeamPrivate *priv = NMT_PAGE_TEAM_GET_PRIVATE(team);
+
+    if (priv->slave_type != NM_TYPE_SETTING_WIRED) {
+        if (connection_type == NM_TYPE_SETTING_INFINIBAND)
+            return TRUE;
+    }
+    if (priv->slave_type != NM_TYPE_SETTING_INFINIBAND) {
+        if (connection_type == NM_TYPE_SETTING_WIRED || connection_type == NM_TYPE_SETTING_WIRELESS
+            || connection_type == NM_TYPE_SETTING_VLAN)
+            return TRUE;
+    }
+
+    return FALSE;
+}
+
+static void
+edit_clicked(NmtNewtButton *button, gpointer user_data)
+{
+    NmtPageTeam *       team = user_data;
+    NmtPageTeamPrivate *priv = NMT_PAGE_TEAM_GET_PRIVATE(team);
+    const char *        config;
+    char *              new_config;
+
+    config = nm_setting_team_get_config(priv->s_team);
+    if (!config)
+        config = "";
+
+    new_config = nmt_newt_edit_string(config);
+
+    if (new_config && !*new_config)
+        nm_clear_g_free(&new_config);
+    g_object_set(G_OBJECT(priv->s_team), NM_SETTING_TEAM_CONFIG, new_config, NULL);
+    g_free(new_config);
+}
+
+static void
+nmt_page_team_constructed(GObject *object)
+{
+    NmtPageTeam *       team = NMT_PAGE_TEAM(object);
+    NmtPageTeamPrivate *priv = NMT_PAGE_TEAM_GET_PRIVATE(team);
+    NmtEditorSection *  section;
+    NmtNewtGrid *       grid;
+    NMSettingTeam *     s_team;
+    NmtNewtWidget *     widget;
+    NMConnection *      conn;
+
+    conn   = nmt_editor_page_get_connection(NMT_EDITOR_PAGE(team));
+    s_team = nm_connection_get_setting_team(conn);
+    if (!s_team) {
+        nm_connection_add_setting(conn, nm_setting_team_new());
+        s_team = nm_connection_get_setting_team(conn);
+    }
+    priv->s_team = s_team;
+
+    section = nmt_editor_section_new(_("TEAM"), NULL, TRUE);
+
+    widget = nmt_newt_grid_new();
+    nmt_editor_grid_append(nmt_editor_section_get_body(section), NULL, widget, NULL);
+
+    grid = NMT_NEWT_GRID(widget);
+
+    widget = nmt_newt_label_new(_("Slaves"));
+    nmt_newt_grid_add(grid, widget, 0, 0);
+
+    widget = nmt_slave_list_new(conn, team_connection_type_filter, team);
+    g_signal_connect(widget, "notify::connections", G_CALLBACK(slaves_changed), team);
+    nmt_newt_grid_add(grid, widget, 0, 1);
+    nmt_newt_widget_set_padding(widget, 0, 0, 0, 1);
+    priv->slaves = NMT_SLAVE_LIST(widget);
+    slaves_changed(G_OBJECT(priv->slaves), NULL, team);
+
+    widget = nmt_newt_label_new(_("JSON configuration"));
+    nmt_newt_grid_add(grid, widget, 0, 2);
+
+    widget =
+        nmt_newt_textbox_new(NMT_NEWT_TEXTBOX_SCROLLABLE | NMT_NEWT_TEXTBOX_SET_BACKGROUND, 60);
+    g_object_bind_property(s_team, NM_SETTING_TEAM_CONFIG, widget, "text", G_BINDING_SYNC_CREATE);
+    nmt_newt_grid_add(grid, widget, 0, 3);
+    nmt_newt_widget_set_padding(widget, 2, 0, 2, 1);
+
+    widget = nmt_newt_button_new(_("Edit..."));
+    g_signal_connect(widget, "clicked", G_CALLBACK(edit_clicked), team);
+    nmt_newt_grid_add(grid, widget, 0, 4);
+
+    nmt_editor_page_add_section(NMT_EDITOR_PAGE(team), section);
+
+    G_OBJECT_CLASS(nmt_page_team_parent_class)->constructed(object);
+}
+
+static void
+nmt_page_team_saved(NmtEditorPage *editor_page)
+{
+    NmtPageTeamPrivate *priv = NMT_PAGE_TEAM_GET_PRIVATE(editor_page);
+
+    nmt_edit_connection_list_recommit(NMT_EDIT_CONNECTION_LIST(priv->slaves));
+}
+
+static void
+nmt_page_team_class_init(NmtPageTeamClass *team_class)
+{
+    GObjectClass *      object_class      = G_OBJECT_CLASS(team_class);
+    NmtEditorPageClass *editor_page_class = NMT_EDITOR_PAGE_CLASS(team_class);
+
+    g_type_class_add_private(team_class, sizeof(NmtPageTeamPrivate));
+
+    object_class->constructed = nmt_page_team_constructed;
+    editor_page_class->saved  = nmt_page_team_saved;
+}
diff --git a/src/nmtui/nmt-page-team.h b/src/nmtui/nmt-page-team.h
new file mode 100644
index 00000000..5eb792ae
--- /dev/null
+++ b/src/nmtui/nmt-page-team.h
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+#ifndef NMT_PAGE_TEAM_H
+#define NMT_PAGE_TEAM_H
+
+#include "nmt-editor-page-device.h"
+
+#define NMT_TYPE_PAGE_TEAM (nmt_page_team_get_type())
+#define NMT_PAGE_TEAM(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NMT_TYPE_PAGE_TEAM, NmtPageTeam))
+#define NMT_PAGE_TEAM_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NMT_TYPE_PAGE_TEAM, NmtPageTeamClass))
+#define NMT_IS_PAGE_TEAM(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMT_TYPE_PAGE_TEAM))
+#define NMT_IS_PAGE_TEAM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NMT_TYPE_PAGE_TEAM))
+#define NMT_PAGE_TEAM_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NMT_TYPE_PAGE_TEAM, NmtPageTeamClass))
+
+typedef struct {
+    NmtEditorPageDevice parent;
+
+} NmtPageTeam;
+
+typedef struct {
+    NmtEditorPageDeviceClass parent;
+
+} NmtPageTeamClass;
+
+GType nmt_page_team_get_type(void);
+
+NmtEditorPage *nmt_page_team_new(NMConnection *conn, NmtDeviceEntry *deventry);
+
+#endif /* NMT_PAGE_TEAM_H */
diff --git a/src/nmtui/nmt-page-vlan.c b/src/nmtui/nmt-page-vlan.c
new file mode 100644
index 00000000..299a7267
--- /dev/null
+++ b/src/nmtui/nmt-page-vlan.c
@@ -0,0 +1,121 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+/**
+ * SECTION:nmt-page-vlan
+ * @short_description: The editor page for VLAN connections
+ */
+
+#include "libnm-client-aux-extern/nm-default-client.h"
+
+#include "nm-editor-bindings.h"
+
+#include <linux/if_ether.h>
+
+#include "nmt-page-vlan.h"
+#include "nmt-device-entry.h"
+#include "nmt-mac-entry.h"
+#include "nmt-mtu-entry.h"
+
+G_DEFINE_TYPE(NmtPageVlan, nmt_page_vlan, NMT_TYPE_EDITOR_PAGE_DEVICE)
+
+NmtEditorPage *
+nmt_page_vlan_new(NMConnection *conn, NmtDeviceEntry *deventry)
+{
+    return g_object_new(NMT_TYPE_PAGE_VLAN, "connection", conn, "device-entry", deventry, NULL);
+}
+
+static void
+nmt_page_vlan_init(NmtPageVlan *vlan)
+{}
+
+static gboolean
+vlan_device_filter(NmtDeviceEntry *deventry, NMDevice *device, gpointer user_data)
+{
+    // FIXME
+    return NM_IS_DEVICE_ETHERNET(device);
+}
+
+static void
+nmt_page_vlan_constructed(GObject *object)
+{
+    NmtPageVlan *     vlan = NMT_PAGE_VLAN(object);
+    NmtEditorSection *section;
+    NmtEditorGrid *   grid;
+    NMSettingWired *  s_wired;
+    NMSettingVlan *   s_vlan;
+    NmtNewtWidget *   widget, *parent, *id_entry;
+    NMConnection *    conn;
+
+    conn   = nmt_editor_page_get_connection(NMT_EDITOR_PAGE(vlan));
+    s_vlan = nm_connection_get_setting_vlan(conn);
+    if (!s_vlan) {
+        nm_connection_add_setting(conn, nm_setting_vlan_new());
+        s_vlan = nm_connection_get_setting_vlan(conn);
+    }
+    s_wired = nm_connection_get_setting_wired(conn);
+    if (!s_wired) {
+        nm_connection_add_setting(conn, nm_setting_wired_new());
+        s_wired = nm_connection_get_setting_wired(conn);
+    }
+
+    section = nmt_editor_section_new(_("VLAN"), NULL, TRUE);
+    grid    = nmt_editor_section_get_body(section);
+
+    nm_editor_bind_vlan_name(s_vlan, nm_connection_get_setting_connection(conn));
+
+    widget = parent = nmt_device_entry_new(_("Parent"), 40, G_TYPE_NONE);
+    nmt_device_entry_set_device_filter(NMT_DEVICE_ENTRY(widget), vlan_device_filter, vlan);
+    g_object_bind_property(s_vlan,
+                           NM_SETTING_VLAN_PARENT,
+                           widget,
+                           "interface-name",
+                           G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+    g_object_bind_property(s_wired,
+                           NM_SETTING_WIRED_MAC_ADDRESS,
+                           widget,
+                           "mac-address",
+                           G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+    nmt_editor_grid_append(grid, NULL, widget, NULL);
+
+    widget = id_entry = nmt_newt_entry_numeric_new(8, 0, 4094);
+    g_object_bind_property(s_vlan,
+                           NM_SETTING_VLAN_ID,
+                           widget,
+                           "text",
+                           G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+    nmt_editor_grid_append(grid, _("VLAN id"), widget, NULL);
+
+    nmt_editor_grid_append(grid, NULL, nmt_newt_separator_new(), NULL);
+
+    widget = nmt_mac_entry_new(40, ETH_ALEN, NMT_MAC_ENTRY_TYPE_CLONED);
+    g_object_bind_property(s_wired,
+                           NM_SETTING_WIRED_CLONED_MAC_ADDRESS,
+                           widget,
+                           "mac-address",
+                           G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+    nmt_editor_grid_append(grid, _("Cloned MAC address"), widget, NULL);
+
+    widget = nmt_mtu_entry_new();
+    g_object_bind_property(s_wired,
+                           NM_SETTING_WIRED_MTU,
+                           widget,
+                           "mtu",
+                           G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+    nmt_editor_grid_append(grid, _("MTU"), widget, NULL);
+
+    nmt_editor_page_add_section(NMT_EDITOR_PAGE(vlan), section);
+
+    G_OBJECT_CLASS(nmt_page_vlan_parent_class)->constructed(object);
+}
+
+static void
+nmt_page_vlan_class_init(NmtPageVlanClass *vlan_class)
+{
+    GObjectClass *object_class = G_OBJECT_CLASS(vlan_class);
+
+    /* virtual methods */
+    object_class->constructed = nmt_page_vlan_constructed;
+}
diff --git a/src/nmtui/nmt-page-vlan.h b/src/nmtui/nmt-page-vlan.h
new file mode 100644
index 00000000..deebda00
--- /dev/null
+++ b/src/nmtui/nmt-page-vlan.h
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+#ifndef NMT_PAGE_VLAN_H
+#define NMT_PAGE_VLAN_H
+
+#include "nmt-editor-page-device.h"
+
+#define NMT_TYPE_PAGE_VLAN (nmt_page_vlan_get_type())
+#define NMT_PAGE_VLAN(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NMT_TYPE_PAGE_VLAN, NmtPageVlan))
+#define NMT_PAGE_VLAN_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NMT_TYPE_PAGE_VLAN, NmtPageVlanClass))
+#define NMT_IS_PAGE_VLAN(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMT_TYPE_PAGE_VLAN))
+#define NMT_IS_PAGE_VLAN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NMT_TYPE_PAGE_VLAN))
+#define NMT_PAGE_VLAN_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NMT_TYPE_PAGE_VLAN, NmtPageVlanClass))
+
+typedef struct {
+    NmtEditorPageDevice parent;
+
+} NmtPageVlan;
+
+typedef struct {
+    NmtEditorPageDeviceClass parent;
+
+} NmtPageVlanClass;
+
+GType nmt_page_vlan_get_type(void);
+
+NmtEditorPage *nmt_page_vlan_new(NMConnection *conn, NmtDeviceEntry *deventry);
+
+#endif /* NMT_PAGE_VLAN_H */
diff --git a/src/nmtui/nmt-page-wifi.c b/src/nmtui/nmt-page-wifi.c
new file mode 100644
index 00000000..247a0e4e
--- /dev/null
+++ b/src/nmtui/nmt-page-wifi.c
@@ -0,0 +1,409 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+/**
+ * SECTION:nmt-page-wifi
+ * @short_description: The editor page for Wi-Fi connections
+ *
+ * #NmtPageWifi is the editor page for Wi-Fi connections, which
+ * includes both #NMSettingWireless and #NMSettingWirelessSecurity
+ * properties.
+ */
+
+#include "libnm-client-aux-extern/nm-default-client.h"
+
+#include <stdlib.h>
+#include <linux/if_ether.h>
+
+#include "nmt-page-wifi.h"
+#include "nmt-mac-entry.h"
+#include "nmt-mtu-entry.h"
+#include "nmt-password-fields.h"
+
+#include "nm-editor-bindings.h"
+
+G_DEFINE_TYPE(NmtPageWifi, nmt_page_wifi, NMT_TYPE_EDITOR_PAGE_DEVICE)
+
+#define NMT_PAGE_WIFI_GET_PRIVATE(o) \
+    (G_TYPE_INSTANCE_GET_PRIVATE((o), NMT_TYPE_PAGE_WIFI, NmtPageWifiPrivate))
+
+typedef struct {
+    NMSettingWirelessSecurity *s_wsec;
+
+} NmtPageWifiPrivate;
+
+NmtEditorPage *
+nmt_page_wifi_new(NMConnection *conn, NmtDeviceEntry *deventry)
+{
+    return g_object_new(NMT_TYPE_PAGE_WIFI, "connection", conn, "device-entry", deventry, NULL);
+}
+
+static void
+nmt_page_wifi_init(NmtPageWifi *wifi)
+{}
+
+static NmtNewtPopupEntry wifi_mode[] = {{NC_("Wi-Fi", "Client"), NM_SETTING_WIRELESS_MODE_INFRA},
+                                        {N_("Access Point"), NM_SETTING_WIRELESS_MODE_AP},
+                                        {N_("Ad-Hoc Network"), NM_SETTING_WIRELESS_MODE_ADHOC},
+                                        {NULL, NULL}};
+
+static NmtNewtPopupEntry wifi_band[] = {{NC_("Wi-Fi", "Automatic"), NULL},
+                                        /* 802.11a Wi-Fi network */
+                                        {N_("A (5 GHz)"), "a"},
+                                        /* 802.11b / 802.11g Wi-Fi network */
+                                        {N_("B/G (2.4 GHz)"), "bg"},
+                                        {NULL, NULL}};
+
+static NmtNewtPopupEntry wifi_security[] = {{NC_("Wi-Fi security", "None"), "none"},
+                                            {N_("WPA & WPA2 Personal"), "wpa-personal"},
+                                            {N_("WPA3 Personal"), "wpa3-personal"},
+                                            {N_("WPA & WPA2 Enterprise"), "wpa-enterprise"},
+                                            {N_("WEP 40/128-bit Key (Hex or ASCII)"), "wep-key"},
+                                            {N_("WEP 128-bit Passphrase"), "wep-passphrase"},
+                                            {N_("Dynamic WEP (802.1x)"), "dynamic-wep"},
+                                            {N_("LEAP"), "leap"},
+                                            {N_("Enhanced Open (OWE)"), "owe"},
+                                            {NULL, NULL}};
+
+static NmtNewtPopupEntry wep_index[] = {{NC_("WEP key index", "1 (Default)"), "1"},
+                                        {NC_("WEP key index", "2"), "2"},
+                                        {NC_("WEP key index", "3"), "3"},
+                                        {NC_("WEP key index", "4"), "4"},
+                                        {NULL, NULL}};
+
+static NmtNewtPopupEntry wep_auth[] = {{N_("Open System"), "open"},
+                                       {N_("Shared Key"), "shared"},
+                                       {NULL, NULL}};
+
+static gboolean
+mode_transform_to_band_visibility(GBinding *    binding,
+                                  const GValue *source_value,
+                                  GValue *      target_value,
+                                  gpointer      user_data)
+{
+    if (!g_strcmp0(g_value_get_string(source_value), NM_SETTING_WIRELESS_MODE_INFRA))
+        g_value_set_boolean(target_value, FALSE);
+    else
+        g_value_set_boolean(target_value, TRUE);
+    return TRUE;
+}
+
+static gboolean
+band_transform_to_channel_visibility(GBinding *    binding,
+                                     const GValue *source_value,
+                                     GValue *      target_value,
+                                     gpointer      user_data)
+{
+    g_value_set_boolean(target_value, g_value_get_string(source_value) != NULL);
+    return TRUE;
+}
+
+static gboolean
+ssid_transform_to_entry(GBinding *    binding,
+                        const GValue *source_value,
+                        GValue *      target_value,
+                        gpointer      user_data)
+{
+    GBytes *ssid;
+    char *  utf8;
+
+    ssid = g_value_get_boxed(source_value);
+    if (ssid)
+        utf8 = nm_utils_ssid_to_utf8(g_bytes_get_data(ssid, NULL), g_bytes_get_size(ssid));
+    else
+        utf8 = g_strdup("");
+    g_value_take_string(target_value, utf8);
+    return TRUE;
+}
+
+static gboolean
+ssid_transform_from_entry(GBinding *    binding,
+                          const GValue *source_value,
+                          GValue *      target_value,
+                          gpointer      user_data)
+{
+    NMSettingWireless *s_wireless = user_data;
+    const char *       text;
+    GBytes *           old_ssid, *ssid;
+    char *             utf8;
+
+    text = g_value_get_string(source_value);
+
+    old_ssid = nm_setting_wireless_get_ssid(s_wireless);
+    if (old_ssid)
+        utf8 = nm_utils_ssid_to_utf8(g_bytes_get_data(old_ssid, NULL), g_bytes_get_size(old_ssid));
+    else
+        utf8 = g_strdup("");
+
+    if (!g_strcmp0(text, utf8)) {
+        g_free(utf8);
+        return FALSE;
+    }
+    g_free(utf8);
+
+    ssid = g_bytes_new(text, strlen(text));
+    g_value_take_boxed(target_value, ssid);
+    return TRUE;
+}
+
+static void
+nmt_page_wifi_constructed(GObject *object)
+{
+    NmtPageWifiPrivate *       priv = NMT_PAGE_WIFI_GET_PRIVATE(object);
+    NmtPageWifi *              wifi = NMT_PAGE_WIFI(object);
+    NmtDeviceEntry *           deventry;
+    NmtEditorSection *         section;
+    NmtEditorGrid *            grid;
+    NMSettingWireless *        s_wireless;
+    NMSettingWirelessSecurity *s_wsec;
+    NmtNewtWidget *            widget, *hbox, *subgrid;
+    NmtNewtWidget *            mode, *band, *security, *entry;
+    NmtNewtStack *             stack;
+    NMConnection *             conn;
+
+    conn       = nmt_editor_page_get_connection(NMT_EDITOR_PAGE(wifi));
+    s_wireless = nm_connection_get_setting_wireless(conn);
+    if (!s_wireless) {
+        nm_connection_add_setting(conn, nm_setting_wireless_new());
+        s_wireless = nm_connection_get_setting_wireless(conn);
+    }
+
+    s_wsec = nm_connection_get_setting_wireless_security(conn);
+    if (!s_wsec) {
+        /* It makes things simpler if we always have a
+         * NMSettingWirelessSecurity; we'll hold a ref on one, and add
+         * it to and remove it from the connection as needed.
+         */
+        s_wsec = NM_SETTING_WIRELESS_SECURITY(nm_setting_wireless_security_new());
+    }
+    priv->s_wsec = g_object_ref_sink(s_wsec);
+
+    deventry = nmt_editor_page_device_get_device_entry(NMT_EDITOR_PAGE_DEVICE(object));
+    g_object_bind_property(s_wireless,
+                           NM_SETTING_WIRELESS_MAC_ADDRESS,
+                           deventry,
+                           "mac-address",
+                           G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+
+    section = nmt_editor_section_new(_("WI-FI"), NULL, TRUE);
+    grid    = nmt_editor_section_get_body(section);
+
+    widget = nmt_newt_entry_new(40, NMT_NEWT_ENTRY_NONEMPTY);
+    g_object_bind_property_full(s_wireless,
+                                NM_SETTING_WIRELESS_SSID,
+                                widget,
+                                "text",
+                                G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE,
+                                ssid_transform_to_entry,
+                                ssid_transform_from_entry,
+                                s_wireless,
+                                NULL);
+    nmt_editor_grid_append(grid, _("SSID"), widget, NULL);
+
+    widget = nmt_newt_popup_new(wifi_mode);
+    g_object_bind_property(s_wireless,
+                           NM_SETTING_WIRELESS_MODE,
+                           widget,
+                           "active-id",
+                           G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+    nmt_editor_grid_append(grid, _("Mode"), widget, NULL);
+    mode = widget;
+
+    hbox   = nmt_newt_grid_new();
+    widget = nmt_newt_popup_new(wifi_band);
+    g_object_bind_property(s_wireless,
+                           NM_SETTING_WIRELESS_BAND,
+                           widget,
+                           "active-id",
+                           G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+    nmt_newt_grid_add(NMT_NEWT_GRID(hbox), widget, 0, 0);
+    band = widget;
+
+    widget = nmt_newt_entry_numeric_new(10, 0, 255);
+    g_object_bind_property(s_wireless,
+                           NM_SETTING_WIRELESS_CHANNEL,
+                           widget,
+                           "text",
+                           G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+    nmt_newt_grid_add(NMT_NEWT_GRID(hbox), widget, 1, 0);
+    nmt_newt_widget_set_padding(widget, 1, 0, 0, 0);
+
+    g_object_bind_property_full(band,
+                                "active-id",
+                                widget,
+                                "visible",
+                                G_BINDING_SYNC_CREATE,
+                                band_transform_to_channel_visibility,
+                                NULL,
+                                NULL,
+                                NULL);
+    g_object_bind_property_full(mode,
+                                "active-id",
+                                hbox,
+                                "visible",
+                                G_BINDING_SYNC_CREATE,
+                                mode_transform_to_band_visibility,
+                                NULL,
+                                NULL,
+                                NULL);
+    nmt_editor_grid_append(grid, _("Channel"), hbox, NULL);
+
+    nmt_editor_grid_append(grid, NULL, nmt_newt_separator_new(), NULL);
+
+    widget = nmt_newt_popup_new(wifi_security);
+    nmt_editor_grid_append(grid, _("Security"), widget, NULL);
+    security = widget;
+
+    widget = nmt_newt_stack_new();
+    stack  = NMT_NEWT_STACK(widget);
+
+    /* none */
+    subgrid = nmt_editor_grid_new();
+    nmt_newt_stack_add(stack, "none", subgrid);
+
+    /* wpa-personal */
+    subgrid = nmt_editor_grid_new();
+    widget  = nmt_password_fields_new(40, NMT_PASSWORD_FIELDS_SHOW_PASSWORD);
+    g_object_bind_property(s_wsec,
+                           NM_SETTING_WIRELESS_SECURITY_PSK,
+                           widget,
+                           "password",
+                           G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
+    nmt_editor_grid_append(NMT_EDITOR_GRID(subgrid), _("Password"), widget, NULL);
+    nmt_newt_stack_add(stack, "wpa-personal", subgrid);
+    nmt_newt_stack_add(stack, "wpa3-personal", subgrid);
+
+    /* "wpa-enterprise" */
+    // FIXME
+    widget = nmt_newt_label_new(_("(No support for wpa-enterprise yet...)"));
+    nmt_newt_stack_add(stack, "wpa-enterprise", widget);
+
+    /* wep-key */
+    subgrid = nmt_editor_grid_new();
+
+    widget = entry = nmt_password_fields_new(40, NMT_PASSWORD_FIELDS_SHOW_PASSWORD);
+    nmt_editor_grid_append(NMT_EDITOR_GRID(subgrid), _("Key"), widget, NULL);
+
+    widget = nmt_newt_popup_new(wep_index);
+    nmt_editor_grid_append(NMT_EDITOR_GRID(subgrid), _("WEP index"), widget, NULL);
+
+    nm_editor_bind_wireless_security_wep_key(s_wsec,
+                                             entry,
+                                             "password",
+                                             widget,
+                                             "active",
+                                             G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+
+    widget = nmt_newt_popup_new(wep_auth);
+    nmt_editor_grid_append(NMT_EDITOR_GRID(subgrid), _("Authentication"), widget, NULL);
+
+    nmt_newt_stack_add(stack, "wep-key", subgrid);
+
+    /* wep-passphrase */
+    subgrid = nmt_editor_grid_new();
+
+    widget = entry = nmt_password_fields_new(40, NMT_PASSWORD_FIELDS_SHOW_PASSWORD);
+    nmt_editor_grid_append(NMT_EDITOR_GRID(subgrid), _("Password"), widget, NULL);
+
+    widget = nmt_newt_popup_new(wep_index);
+    nmt_editor_grid_append(NMT_EDITOR_GRID(subgrid), _("WEP index"), widget, NULL);
+
+    nm_editor_bind_wireless_security_wep_key(s_wsec,
+                                             entry,
+                                             "password",
+                                             widget,
+                                             "active",
+                                             G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+
+    widget = nmt_newt_popup_new(wep_auth);
+    nmt_editor_grid_append(NMT_EDITOR_GRID(subgrid), _("Authentication"), widget, NULL);
+
+    nmt_newt_stack_add(stack, "wep-passphrase", subgrid);
+
+    /* "dynamic-wep" */
+    // FIXME
+    widget = nmt_newt_label_new(_("(No support for dynamic-wep yet...)"));
+    nmt_newt_stack_add(stack, "dynamic-wep", widget);
+
+    /* leap */
+    subgrid = nmt_editor_grid_new();
+
+    widget = nmt_newt_entry_new(40, NMT_NEWT_ENTRY_NONEMPTY);
+    nmt_editor_grid_append(NMT_EDITOR_GRID(subgrid), _("Username"), widget, NULL);
+    g_object_bind_property(s_wsec,
+                           NM_SETTING_WIRELESS_SECURITY_LEAP_USERNAME,
+                           widget,
+                           "text",
+                           G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
+
+    widget = nmt_password_fields_new(40, NMT_PASSWORD_FIELDS_SHOW_PASSWORD);
+    g_object_bind_property(s_wsec,
+                           NM_SETTING_WIRELESS_SECURITY_LEAP_PASSWORD,
+                           widget,
+                           "password",
+                           G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
+    nmt_editor_grid_append(NMT_EDITOR_GRID(subgrid), _("Password"), widget, NULL);
+
+    nmt_newt_stack_add(stack, "leap", subgrid);
+
+    nmt_editor_grid_append(grid, NULL, NMT_NEWT_WIDGET(stack), NULL);
+    g_object_bind_property(security, "active-id", stack, "active-id", G_BINDING_SYNC_CREATE);
+    nm_editor_bind_wireless_security_method(conn,
+                                            s_wsec,
+                                            security,
+                                            "active-id",
+                                            G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+
+    nmt_editor_grid_append(grid, NULL, nmt_newt_separator_new(), NULL);
+
+    widget = nmt_mac_entry_new(40, ETH_ALEN, NMT_MAC_ENTRY_TYPE_MAC);
+    g_object_bind_property(s_wireless,
+                           NM_SETTING_WIRELESS_BSSID,
+                           widget,
+                           "mac-address",
+                           G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+    nmt_editor_grid_append(grid, _("BSSID"), widget, NULL);
+
+    widget = nmt_mac_entry_new(40, ETH_ALEN, NMT_MAC_ENTRY_TYPE_CLONED);
+    g_object_bind_property(s_wireless,
+                           NM_SETTING_WIRELESS_CLONED_MAC_ADDRESS,
+                           widget,
+                           "mac-address",
+                           G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+    nmt_editor_grid_append(grid, _("Cloned MAC address"), widget, NULL);
+
+    widget = nmt_mtu_entry_new();
+    g_object_bind_property(s_wireless,
+                           NM_SETTING_WIRELESS_MTU,
+                           widget,
+                           "mtu",
+                           G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+    nmt_editor_grid_append(grid, _("MTU"), widget, NULL);
+
+    nmt_editor_page_add_section(NMT_EDITOR_PAGE(wifi), section);
+
+    G_OBJECT_CLASS(nmt_page_wifi_parent_class)->constructed(object);
+}
+
+static void
+nmt_page_wifi_finalize(GObject *object)
+{
+    NmtPageWifiPrivate *priv = NMT_PAGE_WIFI_GET_PRIVATE(object);
+
+    g_clear_object(&priv->s_wsec);
+
+    G_OBJECT_CLASS(nmt_page_wifi_parent_class)->finalize(object);
+}
+
+static void
+nmt_page_wifi_class_init(NmtPageWifiClass *wifi_class)
+{
+    GObjectClass *object_class = G_OBJECT_CLASS(wifi_class);
+
+    g_type_class_add_private(wifi_class, sizeof(NmtPageWifiPrivate));
+
+    object_class->constructed = nmt_page_wifi_constructed;
+    object_class->finalize    = nmt_page_wifi_finalize;
+}
diff --git a/src/nmtui/nmt-page-wifi.h b/src/nmtui/nmt-page-wifi.h
new file mode 100644
index 00000000..c01fdd1d
--- /dev/null
+++ b/src/nmtui/nmt-page-wifi.h
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+#ifndef NMT_PAGE_WIFI_H
+#define NMT_PAGE_WIFI_H
+
+#include "nmt-editor-page-device.h"
+
+#define NMT_TYPE_PAGE_WIFI (nmt_page_wifi_get_type())
+#define NMT_PAGE_WIFI(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NMT_TYPE_PAGE_WIFI, NmtPageWifi))
+#define NMT_PAGE_WIFI_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NMT_TYPE_PAGE_WIFI, NmtPageWifiClass))
+#define NMT_IS_PAGE_WIFI(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMT_TYPE_PAGE_WIFI))
+#define NMT_IS_PAGE_WIFI_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NMT_TYPE_PAGE_WIFI))
+#define NMT_PAGE_WIFI_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NMT_TYPE_PAGE_WIFI, NmtPageWifiClass))
+
+typedef struct {
+    NmtEditorPageDevice parent;
+
+} NmtPageWifi;
+
+typedef struct {
+    NmtEditorPageDeviceClass parent;
+
+} NmtPageWifiClass;
+
+GType nmt_page_wifi_get_type(void);
+
+NmtEditorPage *nmt_page_wifi_new(NMConnection *conn, NmtDeviceEntry *deventry);
+
+#endif /* NMT_PAGE_WIFI_H */
diff --git a/src/nmtui/nmt-password-dialog.c b/src/nmtui/nmt-password-dialog.c
new file mode 100644
index 00000000..99b9b3f7
--- /dev/null
+++ b/src/nmtui/nmt-password-dialog.c
@@ -0,0 +1,337 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+/**
+ * SECTION:nmt-password-dialog
+ * @short_description: A password dialog
+ *
+ * #NmtPasswordDialog is the password dialog used to get connection
+ * secrets when activating a connection.
+ */
+
+#include "libnm-client-aux-extern/nm-default-client.h"
+
+#include "nmt-password-dialog.h"
+#include "libnmc-base/nm-secret-agent-simple.h"
+#include "nmtui.h"
+
+G_DEFINE_TYPE(NmtPasswordDialog, nmt_password_dialog, NMT_TYPE_NEWT_FORM)
+
+#define NMT_PASSWORD_DIALOG_GET_PRIVATE(o) \
+    (G_TYPE_INSTANCE_GET_PRIVATE((o), NMT_TYPE_PASSWORD_DIALOG, NmtPasswordDialogPrivate))
+
+typedef struct {
+    char *     request_id;
+    char *     prompt;
+    GPtrArray *secrets;
+    GPtrArray *entries;
+
+    NmtNewtWidget *ok, *cancel;
+    NmtNewtWidget *last_entry;
+    NmtNewtWidget *secret_grid;
+
+    gboolean succeeded;
+} NmtPasswordDialogPrivate;
+
+enum {
+    PROP_0,
+    PROP_REQUEST_ID,
+    PROP_PROMPT,
+    PROP_SECRETS,
+
+    LAST_PROP
+};
+
+/**
+ * nmt_password_dialog_new:
+ * @request_id: the request ID from the #NMSecretAgentSimple
+ * @title: the dialog title
+ * @prompt: the prompt text to display
+ * @secrets: (element-type #NMSecretAgentSimpleSecret): the secrets requested
+ *
+ * Creates a new #NmtPasswordDialog to request passwords from
+ * the user.
+ *
+ * Returns: a new #NmtPasswordDialog.
+ */
+NmtNewtForm *
+nmt_password_dialog_new(const char *request_id,
+                        const char *title,
+                        const char *prompt,
+                        GPtrArray * secrets)
+{
+    return g_object_new(NMT_TYPE_PASSWORD_DIALOG,
+                        "request-id",
+                        request_id,
+                        "title",
+                        title,
+                        "prompt",
+                        prompt,
+                        "secrets",
+                        secrets,
+                        "escape-exits",
+                        TRUE,
+                        NULL);
+}
+
+static void
+nmt_password_dialog_init(NmtPasswordDialog *dialog)
+{
+    NmtPasswordDialogPrivate *priv = NMT_PASSWORD_DIALOG_GET_PRIVATE(dialog);
+
+    priv->entries = g_ptr_array_new();
+}
+
+static void
+maybe_save_input_and_exit(NmtNewtWidget *widget, gpointer dialog)
+{
+    NmtPasswordDialogPrivate *priv = NMT_PASSWORD_DIALOG_GET_PRIVATE(dialog);
+    int                       i;
+
+    /* This gets invoked when the user types Return in the final entry,
+     * but the form may not be fully valid in that case.
+     */
+    if (!nmt_newt_widget_get_valid(priv->secret_grid))
+        return;
+
+    priv->succeeded = TRUE;
+
+    for (i = 0; i < priv->secrets->len; i++) {
+        NMSecretAgentSimpleSecret *secret = priv->secrets->pdata[i];
+
+        g_free(secret->value);
+        g_object_get(priv->entries->pdata[i], "text", &secret->value, NULL);
+    }
+
+    nmt_newt_form_quit(nmt_newt_widget_get_form(widget));
+}
+
+static void
+nmt_password_dialog_constructed(GObject *object)
+{
+    NmtPasswordDialog *       dialog = NMT_PASSWORD_DIALOG(object);
+    NmtPasswordDialogPrivate *priv   = NMT_PASSWORD_DIALOG_GET_PRIVATE(dialog);
+    NmtNewtWidget *           widget;
+    NmtNewtGrid *             grid, *secret_grid;
+    NmtNewtButtonBox *        bbox;
+    int                       i;
+
+    widget = nmt_newt_grid_new();
+    nmt_newt_form_set_content(NMT_NEWT_FORM(dialog), widget);
+    grid = NMT_NEWT_GRID(widget);
+
+    widget = nmt_newt_textbox_new(0, 60);
+    nmt_newt_textbox_set_text(NMT_NEWT_TEXTBOX(widget), priv->prompt);
+    nmt_newt_grid_add(grid, widget, 0, 0);
+
+    widget = nmt_newt_grid_new();
+    nmt_newt_grid_add(grid, widget, 0, 1);
+    nmt_newt_widget_set_padding(widget, 0, 1, 0, 1);
+    priv->secret_grid = widget;
+    secret_grid       = NMT_NEWT_GRID(widget);
+
+    for (i = 0; i < priv->secrets->len; i++) {
+        NMSecretAgentSimpleSecret *secret = priv->secrets->pdata[i];
+        NmtNewtEntryFlags          flags;
+
+        widget = nmt_newt_label_new(secret->pretty_name);
+        nmt_newt_grid_add(secret_grid, widget, 0, i);
+        nmt_newt_widget_set_padding(widget, 4, 0, 1, 0);
+
+        flags = NMT_NEWT_ENTRY_NONEMPTY;
+        if (secret->is_secret)
+            flags |= NMT_NEWT_ENTRY_PASSWORD;
+        widget = nmt_newt_entry_new(30, flags);
+        if (secret->value)
+            nmt_newt_entry_set_text(NMT_NEWT_ENTRY(widget), secret->value);
+        nmt_newt_grid_add(secret_grid, widget, 1, i);
+        g_ptr_array_add(priv->entries, widget);
+
+        if (i == priv->secrets->len - 1) {
+            priv->last_entry = widget;
+            g_signal_connect(widget, "activated", G_CALLBACK(maybe_save_input_and_exit), dialog);
+        }
+    }
+
+    widget = nmt_newt_button_box_new(NMT_NEWT_BUTTON_BOX_HORIZONTAL);
+    nmt_newt_grid_add(grid, widget, 0, 2);
+    bbox = NMT_NEWT_BUTTON_BOX(widget);
+
+    priv->cancel = nmt_newt_button_box_add_end(NMT_NEWT_BUTTON_BOX(bbox), _("Cancel"));
+    nmt_newt_widget_set_exit_on_activate(priv->cancel, TRUE);
+
+    priv->ok = nmt_newt_button_box_add_end(NMT_NEWT_BUTTON_BOX(bbox), _("OK"));
+    g_signal_connect(priv->ok, "activated", G_CALLBACK(maybe_save_input_and_exit), dialog);
+    g_object_bind_property(priv->secret_grid,
+                           "valid",
+                           priv->ok,
+                           "sensitive",
+                           G_BINDING_SYNC_CREATE);
+
+    G_OBJECT_CLASS(nmt_password_dialog_parent_class)->constructed(object);
+}
+
+static void
+nmt_password_dialog_finalize(GObject *object)
+{
+    NmtPasswordDialogPrivate *priv = NMT_PASSWORD_DIALOG_GET_PRIVATE(object);
+
+    g_free(priv->request_id);
+    g_free(priv->prompt);
+    nm_clear_pointer(&priv->entries, g_ptr_array_unref);
+    nm_clear_pointer(&priv->secrets, g_ptr_array_unref);
+
+    G_OBJECT_CLASS(nmt_password_dialog_parent_class)->finalize(object);
+}
+
+static void
+nmt_password_dialog_set_property(GObject *     object,
+                                 guint         prop_id,
+                                 const GValue *value,
+                                 GParamSpec *  pspec)
+{
+    NmtPasswordDialogPrivate *priv = NMT_PASSWORD_DIALOG_GET_PRIVATE(object);
+
+    switch (prop_id) {
+    case PROP_REQUEST_ID:
+        priv->request_id = g_value_dup_string(value);
+        break;
+    case PROP_PROMPT:
+        priv->prompt = g_value_dup_string(value);
+        break;
+    case PROP_SECRETS:
+        priv->secrets = g_value_dup_boxed(value);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nmt_password_dialog_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+    NmtPasswordDialogPrivate *priv = NMT_PASSWORD_DIALOG_GET_PRIVATE(object);
+
+    switch (prop_id) {
+    case PROP_REQUEST_ID:
+        g_value_set_string(value, priv->request_id);
+        break;
+    case PROP_PROMPT:
+        g_value_set_string(value, priv->prompt);
+        break;
+    case PROP_SECRETS:
+        g_value_set_boxed(value, priv->secrets);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nmt_password_dialog_class_init(NmtPasswordDialogClass *dialog_class)
+{
+    GObjectClass *object_class = G_OBJECT_CLASS(dialog_class);
+
+    g_type_class_add_private(dialog_class, sizeof(NmtPasswordDialogPrivate));
+
+    /* virtual methods */
+    object_class->constructed  = nmt_password_dialog_constructed;
+    object_class->set_property = nmt_password_dialog_set_property;
+    object_class->get_property = nmt_password_dialog_get_property;
+    object_class->finalize     = nmt_password_dialog_finalize;
+
+    /**
+     * NmtPasswordDialog:request-id:
+     *
+     * The request ID from the #NMSecretAgentSimple
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_REQUEST_ID,
+        g_param_spec_string("request-id",
+                            "",
+                            "",
+                            NULL,
+                            G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+    /**
+     * NmtPasswordDialog:prompt:
+     *
+     * The prompt text.
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_PROMPT,
+        g_param_spec_string("prompt",
+                            "",
+                            "",
+                            NULL,
+                            G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+    /**
+     * NmtPasswordDialog:secrets:
+     *
+     * The array of request secrets
+     *
+     * Element-Type: #NMSecretAgentSimpleSecret.
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_SECRETS,
+        g_param_spec_boxed("secrets",
+                           "",
+                           "",
+                           G_TYPE_PTR_ARRAY,
+                           G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+}
+
+/**
+ * nmt_password_dialog_succeeded:
+ * @dialog: the #NmtPasswordDialog
+ *
+ * After the dialog has exited, returns %TRUE if the user clicked
+ * "OK", %FALSE if "Cancel".
+ *
+ * Returns: whether the dialog succeeded.
+ */
+gboolean
+nmt_password_dialog_succeeded(NmtPasswordDialog *dialog)
+{
+    NmtPasswordDialogPrivate *priv = NMT_PASSWORD_DIALOG_GET_PRIVATE(dialog);
+
+    return priv->succeeded;
+}
+
+/**
+ * nmt_password_dialog_get_request_id:
+ * @dialog: the #NmtPasswordDialog
+ *
+ * Gets the dialog's request ID.
+ *
+ * Returns: the dialog's request ID.
+ */
+const char *
+nmt_password_dialog_get_request_id(NmtPasswordDialog *dialog)
+{
+    NmtPasswordDialogPrivate *priv = NMT_PASSWORD_DIALOG_GET_PRIVATE(dialog);
+
+    return priv->request_id;
+}
+
+/**
+ * nmt_password_dialog_get_secrets:
+ * @dialog: the #NmtPasswordDialog
+ *
+ * Gets the dialog's secrets array.
+ *
+ * Returns: (transfer none): the dialog's secrets array.
+ */
+GPtrArray *
+nmt_password_dialog_get_secrets(NmtPasswordDialog *dialog)
+{
+    NmtPasswordDialogPrivate *priv = NMT_PASSWORD_DIALOG_GET_PRIVATE(dialog);
+
+    return priv->secrets;
+}
diff --git a/src/nmtui/nmt-password-dialog.h b/src/nmtui/nmt-password-dialog.h
new file mode 100644
index 00000000..77e8f292
--- /dev/null
+++ b/src/nmtui/nmt-password-dialog.h
@@ -0,0 +1,44 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+#ifndef NMT_PASSWORD_DIALOG_H
+#define NMT_PASSWORD_DIALOG_H
+
+#include "libnmt-newt/nmt-newt.h"
+
+#define NMT_TYPE_PASSWORD_DIALOG (nmt_password_dialog_get_type())
+#define NMT_PASSWORD_DIALOG(obj) \
+    (G_TYPE_CHECK_INSTANCE_CAST((obj), NMT_TYPE_PASSWORD_DIALOG, NmtPasswordDialog))
+#define NMT_PASSWORD_DIALOG_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NMT_TYPE_PASSWORD_DIALOG, NmtPasswordDialogClass))
+#define NMT_IS_PASSWORD_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMT_TYPE_PASSWORD_DIALOG))
+#define NMT_IS_PASSWORD_DIALOG_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_TYPE((klass), NMT_TYPE_PASSWORD_DIALOG))
+#define NMT_PASSWORD_DIALOG_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NMT_TYPE_PASSWORD_DIALOG, NmtPasswordDialogClass))
+
+typedef struct {
+    NmtNewtForm parent;
+
+} NmtPasswordDialog;
+
+typedef struct {
+    NmtNewtFormClass parent;
+
+} NmtPasswordDialogClass;
+
+GType nmt_password_dialog_get_type(void);
+
+NmtNewtForm *nmt_password_dialog_new(const char *request_id,
+                                     const char *title,
+                                     const char *prompt,
+                                     GPtrArray * secrets);
+
+gboolean nmt_password_dialog_succeeded(NmtPasswordDialog *dialog);
+
+const char *nmt_password_dialog_get_request_id(NmtPasswordDialog *dialog);
+GPtrArray * nmt_password_dialog_get_secrets(NmtPasswordDialog *dialog);
+
+#endif /* NMT_PASSWORD_DIALOG_H */
diff --git a/src/nmtui/nmt-password-fields.c b/src/nmtui/nmt-password-fields.c
new file mode 100644
index 00000000..45073c7e
--- /dev/null
+++ b/src/nmtui/nmt-password-fields.c
@@ -0,0 +1,307 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+/**
+ * SECTION:nmt-password-fields
+ * @short_description: Widgets for password-related data
+ *
+ * #NmtPasswordFields provides an entry to type a password into, followed
+ * optionally by an "Ask for this password every time" checkbox and/or a
+ * "Show password" checkbox that toggles whether the password is visible.
+ */
+
+#include "libnm-client-aux-extern/nm-default-client.h"
+
+#include "nmt-password-fields.h"
+
+G_DEFINE_TYPE(NmtPasswordFields, nmt_password_fields, NMT_TYPE_NEWT_GRID)
+
+#define NMT_PASSWORD_FIELDS_GET_PRIVATE(o) \
+    (G_TYPE_INSTANCE_GET_PRIVATE((o), NMT_TYPE_PASSWORD_FIELDS, NmtPasswordFieldsPrivate))
+
+typedef struct {
+    NmtPasswordFieldsExtras extras;
+
+    NmtNewtEntry *   entry;
+    NmtNewtCheckbox *always_ask;
+    NmtNewtCheckbox *show_password;
+
+    char *init_password;
+
+} NmtPasswordFieldsPrivate;
+
+enum {
+    PROP_0,
+    PROP_WIDTH,
+    PROP_EXTRAS,
+    PROP_PASSWORD,
+    PROP_ALWAYS_ASK,
+    PROP_SHOW_PASSWORD,
+
+    LAST_PROP
+};
+
+/**
+ * NmtPasswordFieldsExtras:
+ * @NMT_PASSWORD_FIELDS_ALWAYS_ASK: show an "Always ask" checkbox
+ * @NMT_PASSWORD_FIELDS_SHOW_PASSWORD: show a "Show password" checkbox
+ *
+ * Extra widgets to include in an #NmtPasswordFields
+ */
+
+/**
+ * nmt_password_fields_new:
+ * @width: width in characters of the password entry
+ * @extras: extra widgets to show
+ *
+ * Creates a new #NmtPasswordFields
+ *
+ * Returns: a new #NmtPasswordFields
+ */
+NmtNewtWidget *
+nmt_password_fields_new(int width, NmtPasswordFieldsExtras extras)
+{
+    return g_object_new(NMT_TYPE_PASSWORD_FIELDS, "width", width, "extras", extras, NULL);
+}
+
+static void
+nmt_password_fields_set_password(NmtPasswordFields *fields, const char *password)
+{
+    NmtPasswordFieldsPrivate *priv = NMT_PASSWORD_FIELDS_GET_PRIVATE(fields);
+
+    if (!g_strcmp0(password, nmt_newt_entry_get_text(priv->entry)))
+        return;
+
+    nmt_newt_entry_set_text(priv->entry, password);
+    g_object_notify(G_OBJECT(fields), "password");
+}
+
+static const char *
+nmt_password_fields_get_password(NmtPasswordFields *fields)
+{
+    NmtPasswordFieldsPrivate *priv = NMT_PASSWORD_FIELDS_GET_PRIVATE(fields);
+
+    return nmt_newt_entry_get_text(priv->entry);
+}
+
+static void
+always_ask_changed(GObject *object, GParamSpec *pspec, gpointer fields)
+{
+    g_object_notify(fields, "always-ask");
+}
+
+static void
+show_password_changed(GObject *object, GParamSpec *pspec, gpointer fields)
+{
+    g_object_notify(fields, "show-password");
+}
+
+static void
+nmt_password_fields_init(NmtPasswordFields *fields)
+{
+    NmtPasswordFieldsPrivate *priv = NMT_PASSWORD_FIELDS_GET_PRIVATE(fields);
+
+    priv->entry = NMT_NEWT_ENTRY(nmt_newt_entry_new(-1, 0));
+    priv->always_ask =
+        NMT_NEWT_CHECKBOX(nmt_newt_checkbox_new(_("Ask for this password every time")));
+    priv->show_password = NMT_NEWT_CHECKBOX(nmt_newt_checkbox_new(_("Show password")));
+}
+
+static void
+nmt_password_fields_constructed(GObject *object)
+{
+    NmtPasswordFieldsPrivate *priv = NMT_PASSWORD_FIELDS_GET_PRIVATE(object);
+    NmtNewtGrid *             grid = NMT_NEWT_GRID(object);
+
+    nmt_newt_grid_add(grid, NMT_NEWT_WIDGET(priv->entry), 0, 0);
+
+    if (priv->extras & NMT_PASSWORD_FIELDS_ALWAYS_ASK) {
+        nmt_newt_grid_add(grid, NMT_NEWT_WIDGET(priv->always_ask), 0, 1);
+        g_signal_connect(priv->always_ask,
+                         "notify::active",
+                         G_CALLBACK(always_ask_changed),
+                         object);
+    } else
+        g_clear_object(&priv->always_ask);
+
+    if (priv->extras & NMT_PASSWORD_FIELDS_SHOW_PASSWORD) {
+        nmt_newt_grid_add(grid, NMT_NEWT_WIDGET(priv->show_password), 0, 2);
+        g_signal_connect(priv->show_password,
+                         "notify::active",
+                         G_CALLBACK(show_password_changed),
+                         object);
+        g_object_bind_property(priv->show_password,
+                               "active",
+                               priv->entry,
+                               "password",
+                               G_BINDING_INVERT_BOOLEAN | G_BINDING_SYNC_CREATE);
+    } else
+        g_clear_object(&priv->show_password);
+
+    g_object_bind_property(priv->entry,
+                           "text",
+                           object,
+                           "password",
+                           G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+
+    G_OBJECT_CLASS(nmt_password_fields_parent_class)->constructed(object);
+}
+
+static void
+nmt_password_fields_finalize(GObject *object)
+{
+    NmtPasswordFieldsPrivate *priv = NMT_PASSWORD_FIELDS_GET_PRIVATE(object);
+
+    if (priv->always_ask) {
+        g_signal_handlers_disconnect_by_func(priv->always_ask,
+                                             G_CALLBACK(always_ask_changed),
+                                             object);
+    }
+    if (priv->show_password) {
+        g_signal_handlers_disconnect_by_func(priv->show_password,
+                                             G_CALLBACK(show_password_changed),
+                                             object);
+    }
+
+    G_OBJECT_CLASS(nmt_password_fields_parent_class)->finalize(object);
+}
+
+static void
+nmt_password_fields_set_property(GObject *     object,
+                                 guint         prop_id,
+                                 const GValue *value,
+                                 GParamSpec *  pspec)
+{
+    NmtPasswordFields *       fields = NMT_PASSWORD_FIELDS(object);
+    NmtPasswordFieldsPrivate *priv   = NMT_PASSWORD_FIELDS_GET_PRIVATE(fields);
+
+    switch (prop_id) {
+    case PROP_WIDTH:
+        nmt_newt_entry_set_width(priv->entry, g_value_get_int(value));
+        break;
+    case PROP_EXTRAS:
+        priv->extras = g_value_get_uint(value);
+        nmt_newt_widget_needs_rebuild(NMT_NEWT_WIDGET(fields));
+        break;
+    case PROP_PASSWORD:
+        nmt_password_fields_set_password(fields, g_value_get_string(value));
+        break;
+    case PROP_ALWAYS_ASK:
+        if (priv->always_ask)
+            nmt_newt_checkbox_set_active(priv->always_ask, g_value_get_boolean(value));
+        break;
+    case PROP_SHOW_PASSWORD:
+        if (priv->show_password)
+            nmt_newt_checkbox_set_active(priv->show_password, g_value_get_boolean(value));
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nmt_password_fields_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+    NmtPasswordFields *       entry = NMT_PASSWORD_FIELDS(object);
+    NmtPasswordFieldsPrivate *priv  = NMT_PASSWORD_FIELDS_GET_PRIVATE(entry);
+
+    switch (prop_id) {
+    case PROP_WIDTH:
+        g_value_set_int(value, nmt_newt_entry_get_width(priv->entry));
+        break;
+    case PROP_EXTRAS:
+        g_value_set_uint(value, priv->extras);
+        break;
+    case PROP_PASSWORD:
+        g_value_set_string(value, nmt_password_fields_get_password(entry));
+        break;
+    case PROP_ALWAYS_ASK:
+        if (priv->always_ask)
+            g_value_set_boolean(value, nmt_newt_checkbox_get_active(priv->always_ask));
+        break;
+    case PROP_SHOW_PASSWORD:
+        if (priv->show_password)
+            g_value_set_boolean(value, nmt_newt_checkbox_get_active(priv->show_password));
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nmt_password_fields_class_init(NmtPasswordFieldsClass *entry_class)
+{
+    GObjectClass *object_class = G_OBJECT_CLASS(entry_class);
+
+    g_type_class_add_private(entry_class, sizeof(NmtPasswordFieldsPrivate));
+
+    /* virtual methods */
+    object_class->constructed  = nmt_password_fields_constructed;
+    object_class->set_property = nmt_password_fields_set_property;
+    object_class->get_property = nmt_password_fields_get_property;
+    object_class->finalize     = nmt_password_fields_finalize;
+
+    /**
+     * NmtPasswordFields:width:
+     *
+     * The width in characters of the password entry
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_WIDTH,
+        g_param_spec_int("width", "", "", -1, 80, -1, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+    /**
+     * NmtPasswordFields:extras:
+     *
+     * The extra widgets to show
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_EXTRAS,
+        g_param_spec_uint("extras",
+                          "",
+                          "",
+                          0,
+                          0xFFFF,
+                          0,
+                          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+    /**
+     * NmtPasswordFields:password:
+     *
+     * The entered password.
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_PASSWORD,
+        g_param_spec_string("password", "", "", NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+    /**
+     * NmtPasswordFields:always-ask:
+     *
+     * The current state of the "Always ask" checkbox.
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_ALWAYS_ASK,
+        g_param_spec_boolean("always-ask",
+                             "",
+                             "",
+                             FALSE,
+                             G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+    /**
+     * NmtPasswordFields:show-password:
+     *
+     * The current state of the "Show password" checkbox.
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_SHOW_PASSWORD,
+        g_param_spec_boolean("show-password",
+                             "",
+                             "",
+                             FALSE,
+                             G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+}
diff --git a/src/nmtui/nmt-password-fields.h b/src/nmtui/nmt-password-fields.h
new file mode 100644
index 00000000..3e3d8145
--- /dev/null
+++ b/src/nmtui/nmt-password-fields.h
@@ -0,0 +1,41 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+#ifndef NMT_PASSWORD_FIELDS_H
+#define NMT_PASSWORD_FIELDS_H
+
+#include "libnmt-newt/nmt-newt.h"
+
+#define NMT_TYPE_PASSWORD_FIELDS (nmt_password_fields_get_type())
+#define NMT_PASSWORD_FIELDS(obj) \
+    (G_TYPE_CHECK_INSTANCE_CAST((obj), NMT_TYPE_PASSWORD_FIELDS, NmtPasswordFields))
+#define NMT_PASSWORD_FIELDS_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NMT_TYPE_PASSWORD_FIELDS, NmtPasswordFieldsClass))
+#define NMT_IS_PASSWORD_FIELDS(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMT_TYPE_PASSWORD_FIELDS))
+#define NMT_IS_PASSWORD_FIELDS_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_TYPE((klass), NMT_TYPE_PASSWORD_FIELDS))
+#define NMT_PASSWORD_FIELDS_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NMT_TYPE_PASSWORD_FIELDS, NmtPasswordFieldsClass))
+
+typedef struct {
+    NmtNewtGrid parent;
+
+} NmtPasswordFields;
+
+typedef struct {
+    NmtNewtGridClass parent;
+
+} NmtPasswordFieldsClass;
+
+GType nmt_password_fields_get_type(void);
+
+typedef enum {
+    NMT_PASSWORD_FIELDS_ALWAYS_ASK    = (1 << 0),
+    NMT_PASSWORD_FIELDS_SHOW_PASSWORD = (1 << 1),
+} NmtPasswordFieldsExtras;
+
+NmtNewtWidget *nmt_password_fields_new(int width, NmtPasswordFieldsExtras extras);
+
+#endif /* NMT_PASSWORD_FIELDS_H */
diff --git a/src/nmtui/nmt-route-editor.c b/src/nmtui/nmt-route-editor.c
new file mode 100644
index 00000000..ffccf38d
--- /dev/null
+++ b/src/nmtui/nmt-route-editor.c
@@ -0,0 +1,186 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+/**
+ * SECTION:nmt-route-editor
+ * @short_description: Route editing dialog
+ *
+ * #NmtRouteEditor implements a form for editing IPv4 or IPv6 routes.
+ * This was implemented as a separate dialog because it seemed too
+ * wide to fit into the main window.
+ */
+
+#include "libnm-client-aux-extern/nm-default-client.h"
+
+#include "nmt-route-editor.h"
+#include "nmt-route-table.h"
+
+G_DEFINE_TYPE(NmtRouteEditor, nmt_route_editor, NMT_TYPE_NEWT_FORM)
+
+#define NMT_ROUTE_EDITOR_GET_PRIVATE(o) \
+    (G_TYPE_INSTANCE_GET_PRIVATE((o), NMT_TYPE_ROUTE_EDITOR, NmtRouteEditorPrivate))
+
+typedef struct {
+    NMSetting *orig_setting;
+    NMSetting *edit_setting;
+
+} NmtRouteEditorPrivate;
+
+enum {
+    PROP_0,
+    PROP_SETTING,
+
+    LAST_PROP
+};
+
+/**
+ * nmt_route_editor_new:
+ * @setting: the #NMSettingIP4Config or #NMSettingIP6Config to edit
+ *
+ * Creates a new #NmtRouteEditor to edit the routes in @setting
+ *
+ * Returns: a new #NmtRouteEditor
+ */
+NmtNewtForm *
+nmt_route_editor_new(NMSetting *setting)
+{
+    return g_object_new(NMT_TYPE_ROUTE_EDITOR, "setting", setting, NULL);
+}
+
+static void
+nmt_route_editor_init(NmtRouteEditor *entry)
+{}
+
+static void
+save_routes_and_exit(NmtNewtButton *button, gpointer user_data)
+{
+    NmtRouteEditor *       editor = user_data;
+    NmtRouteEditorPrivate *priv   = NMT_ROUTE_EDITOR_GET_PRIVATE(editor);
+    GPtrArray *            routes;
+
+    g_object_get(priv->edit_setting, NM_SETTING_IP_CONFIG_ROUTES, &routes, NULL);
+    g_object_set(priv->orig_setting, NM_SETTING_IP_CONFIG_ROUTES, routes, NULL);
+    g_ptr_array_unref(routes);
+
+    nmt_newt_form_quit(NMT_NEWT_FORM(editor));
+}
+
+static void
+nmt_route_editor_constructed(GObject *object)
+{
+    NmtRouteEditor *       editor = NMT_ROUTE_EDITOR(object);
+    NmtRouteEditorPrivate *priv   = NMT_ROUTE_EDITOR_GET_PRIVATE(editor);
+    NmtNewtWidget *        vbox, *routes, *buttons, *ok, *cancel;
+
+    if (G_OBJECT_CLASS(nmt_route_editor_parent_class)->constructed)
+        G_OBJECT_CLASS(nmt_route_editor_parent_class)->constructed(object);
+
+    if (NM_IS_SETTING_IP4_CONFIG(priv->edit_setting))
+        routes = nmt_route_table_new(AF_INET);
+    else
+        routes = nmt_route_table_new(AF_INET6);
+    g_object_bind_property(priv->edit_setting,
+                           NM_SETTING_IP_CONFIG_ROUTES,
+                           routes,
+                           "routes",
+                           G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
+
+    vbox = nmt_newt_grid_new();
+    nmt_newt_grid_add(NMT_NEWT_GRID(vbox), routes, 0, 0);
+
+    buttons = nmt_newt_grid_new();
+    nmt_newt_grid_add(NMT_NEWT_GRID(vbox), buttons, 0, 1);
+    nmt_newt_widget_set_padding(buttons, 0, 1, 0, 0);
+
+    cancel = g_object_ref_sink(nmt_newt_button_new(_("Cancel")));
+    nmt_newt_widget_set_exit_on_activate(cancel, TRUE);
+    nmt_newt_grid_add(NMT_NEWT_GRID(buttons), cancel, 0, 0);
+    nmt_newt_grid_set_flags(NMT_NEWT_GRID(buttons),
+                            cancel,
+                            NMT_NEWT_GRID_EXPAND_X | NMT_NEWT_GRID_ANCHOR_RIGHT
+                                | NMT_NEWT_GRID_FILL_Y);
+
+    ok = g_object_ref_sink(nmt_newt_button_new(_("OK")));
+    g_signal_connect(ok, "clicked", G_CALLBACK(save_routes_and_exit), editor);
+    nmt_newt_grid_add(NMT_NEWT_GRID(buttons), ok, 1, 0);
+    nmt_newt_widget_set_padding(ok, 1, 0, 0, 0);
+    g_object_bind_property(routes, "valid", ok, "sensitive", G_BINDING_SYNC_CREATE);
+
+    nmt_newt_form_set_content(NMT_NEWT_FORM(editor), vbox);
+}
+
+static void
+nmt_route_editor_finalize(GObject *object)
+{
+    NmtRouteEditorPrivate *priv = NMT_ROUTE_EDITOR_GET_PRIVATE(object);
+
+    g_clear_object(&priv->orig_setting);
+    g_clear_object(&priv->edit_setting);
+
+    G_OBJECT_CLASS(nmt_route_editor_parent_class)->finalize(object);
+}
+
+static void
+nmt_route_editor_set_property(GObject *     object,
+                              guint         prop_id,
+                              const GValue *value,
+                              GParamSpec *  pspec)
+{
+    NmtRouteEditorPrivate *priv = NMT_ROUTE_EDITOR_GET_PRIVATE(object);
+
+    switch (prop_id) {
+    case PROP_SETTING:
+        priv->orig_setting = g_value_dup_object(value);
+        priv->edit_setting = nm_setting_duplicate(priv->orig_setting);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nmt_route_editor_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+    NmtRouteEditorPrivate *priv = NMT_ROUTE_EDITOR_GET_PRIVATE(object);
+
+    switch (prop_id) {
+    case PROP_SETTING:
+        g_value_set_object(value, priv->edit_setting);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nmt_route_editor_class_init(NmtRouteEditorClass *entry_class)
+{
+    GObjectClass *object_class = G_OBJECT_CLASS(entry_class);
+
+    g_type_class_add_private(entry_class, sizeof(NmtRouteEditorPrivate));
+
+    /* virtual methods */
+    object_class->constructed  = nmt_route_editor_constructed;
+    object_class->set_property = nmt_route_editor_set_property;
+    object_class->get_property = nmt_route_editor_get_property;
+    object_class->finalize     = nmt_route_editor_finalize;
+
+    /**
+     * NmtRouteEditor:setting:
+     *
+     * The #NMSettingIP4Config or #NMSettingIP6Config whose routes are
+     * being edited.
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_SETTING,
+        g_param_spec_object("setting",
+                            "",
+                            "",
+                            NM_TYPE_SETTING,
+                            G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+}
diff --git a/src/nmtui/nmt-route-editor.h b/src/nmtui/nmt-route-editor.h
new file mode 100644
index 00000000..3255324f
--- /dev/null
+++ b/src/nmtui/nmt-route-editor.h
@@ -0,0 +1,35 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+#ifndef NMT_ROUTE_EDITOR_H
+#define NMT_ROUTE_EDITOR_H
+
+#include "libnmt-newt/nmt-newt.h"
+
+#define NMT_TYPE_ROUTE_EDITOR (nmt_route_editor_get_type())
+#define NMT_ROUTE_EDITOR(obj) \
+    (G_TYPE_CHECK_INSTANCE_CAST((obj), NMT_TYPE_ROUTE_EDITOR, NmtRouteEditor))
+#define NMT_ROUTE_EDITOR_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NMT_TYPE_ROUTE_EDITOR, NmtRouteEditorClass))
+#define NMT_IS_ROUTE_EDITOR(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMT_TYPE_ROUTE_EDITOR))
+#define NMT_IS_ROUTE_EDITOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NMT_TYPE_ROUTE_EDITOR))
+#define NMT_ROUTE_EDITOR_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NMT_TYPE_ROUTE_EDITOR, NmtRouteEditorClass))
+
+typedef struct {
+    NmtNewtForm parent;
+
+} NmtRouteEditor;
+
+typedef struct {
+    NmtNewtFormClass parent;
+
+} NmtRouteEditorClass;
+
+GType nmt_route_editor_get_type(void);
+
+NmtNewtForm *nmt_route_editor_new(NMSetting *setting);
+
+#endif /* NMT_ROUTE_EDITOR_H */
diff --git a/src/nmtui/nmt-route-entry.c b/src/nmtui/nmt-route-entry.c
new file mode 100644
index 00000000..5cd150b0
--- /dev/null
+++ b/src/nmtui/nmt-route-entry.c
@@ -0,0 +1,286 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+/**
+ * SECTION:nmt-route-entry
+ * @short_description: A set of widgets describing a single route.
+ *
+ * #NmtRouteEntry provides a set of three entry widgets, for entering
+ * a network/prefix, a next hop, and a metric.
+ *
+ * This is used as a building block by #NmtRouteTable.
+ */
+
+#include "libnm-client-aux-extern/nm-default-client.h"
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <stdlib.h>
+
+#include "nmt-route-entry.h"
+#include "nmt-ip-entry.h"
+
+#include "nm-editor-bindings.h"
+
+G_DEFINE_TYPE(NmtRouteEntry, nmt_route_entry, NMT_TYPE_NEWT_GRID)
+
+#define NMT_ROUTE_ENTRY_GET_PRIVATE(o) \
+    (G_TYPE_INSTANCE_GET_PRIVATE((o), NMT_TYPE_ROUTE_ENTRY, NmtRouteEntryPrivate))
+
+typedef struct {
+    NmtNewtWidget *dest, *next_hop, *metric;
+
+    int        family;
+    int        ip_entry_width, metric_entry_width;
+    NMIPRoute *route;
+} NmtRouteEntryPrivate;
+
+enum {
+    PROP_0,
+    PROP_FAMILY,
+    PROP_IP_ENTRY_WIDTH,
+    PROP_METRIC_ENTRY_WIDTH,
+    PROP_ROUTE,
+
+    LAST_PROP
+};
+
+/**
+ * nmt_route_entry_new:
+ * @family: the address family, eg %AF_INET
+ * @ip_entry_width: the width in characters for the IP address entries
+ * @metric_entry_width: the width in characters for the metric entry
+ *
+ * Creates a new #NmtRouteEntry
+ *
+ * Returns: a new #NmtRouteEntry
+ */
+NmtNewtWidget *
+nmt_route_entry_new(int family, int ip_entry_width, int metric_entry_width)
+{
+    return g_object_new(NMT_TYPE_ROUTE_ENTRY,
+                        "family",
+                        family,
+                        "ip-entry-width",
+                        ip_entry_width,
+                        "metric-entry-width",
+                        metric_entry_width,
+                        NULL);
+}
+
+static void
+nmt_route_entry_init(NmtRouteEntry *entry)
+{}
+
+static gboolean
+entry_validity_transform_to_warning_label(GBinding *    binding,
+                                          const GValue *source_value,
+                                          GValue *      target_value,
+                                          gpointer      user_data)
+{
+    if (g_value_get_boolean(source_value))
+        g_value_set_string(target_value, " ");
+    else
+        g_value_set_string(target_value, "!");
+    return TRUE;
+}
+
+static NmtNewtWidget *
+create_warning_label(NmtNewtWidget *entry)
+{
+    NmtNewtWidget *label;
+
+    label = g_object_new(NMT_TYPE_NEWT_LABEL, "highlight", TRUE, NULL);
+    g_object_bind_property_full(entry,
+                                "valid",
+                                label,
+                                "text",
+                                G_BINDING_SYNC_CREATE,
+                                entry_validity_transform_to_warning_label,
+                                NULL,
+                                NULL,
+                                NULL);
+    return label;
+}
+
+static void
+nmt_route_entry_constructed(GObject *object)
+{
+    NmtRouteEntryPrivate *priv = NMT_ROUTE_ENTRY_GET_PRIVATE(object);
+    NmtNewtGrid *         grid = NMT_NEWT_GRID(object);
+    NmtNewtWidget *       warning_label;
+
+    priv->dest     = nmt_ip_entry_new(priv->ip_entry_width, priv->family, TRUE, FALSE);
+    priv->next_hop = nmt_ip_entry_new(priv->ip_entry_width, priv->family, FALSE, TRUE);
+    priv->metric = nmt_newt_entry_numeric_new_full(priv->metric_entry_width, 0, G_MAXUINT32, TRUE);
+
+    nmt_newt_grid_add(grid, priv->dest, 0, 0);
+    warning_label = create_warning_label(priv->dest);
+    nmt_newt_grid_add(grid, warning_label, 1, 0);
+
+    nmt_newt_grid_add(grid, priv->next_hop, 2, 0);
+    nmt_newt_widget_set_padding(priv->next_hop, 1, 0, 0, 0);
+    warning_label = create_warning_label(priv->next_hop);
+    nmt_newt_grid_add(grid, warning_label, 3, 0);
+
+    nmt_newt_grid_add(grid, priv->metric, 4, 0);
+    nmt_newt_widget_set_padding(priv->metric, 1, 0, 0, 0);
+
+    nm_editor_bind_ip_route_to_strings(priv->family,
+                                       object,
+                                       "route",
+                                       priv->dest,
+                                       "text",
+                                       priv->next_hop,
+                                       "text",
+                                       priv->metric,
+                                       "text",
+                                       G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+
+    G_OBJECT_CLASS(nmt_route_entry_parent_class)->constructed(object);
+}
+
+static newtComponent
+nmt_route_entry_get_focus_component(NmtNewtWidget *widget)
+{
+    NmtRouteEntryPrivate *priv = NMT_ROUTE_ENTRY_GET_PRIVATE(widget);
+
+    return nmt_newt_widget_get_focus_component(priv->dest);
+}
+
+static void
+nmt_route_entry_finalize(GObject *object)
+{
+    NmtRouteEntryPrivate *priv = NMT_ROUTE_ENTRY_GET_PRIVATE(object);
+
+    nm_clear_pointer(&priv->route, nm_ip_route_unref);
+
+    G_OBJECT_CLASS(nmt_route_entry_parent_class)->finalize(object);
+}
+
+static void
+nmt_route_entry_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+    NmtRouteEntryPrivate *priv = NMT_ROUTE_ENTRY_GET_PRIVATE(object);
+
+    switch (prop_id) {
+    case PROP_FAMILY:
+        priv->family = g_value_get_int(value);
+        break;
+    case PROP_IP_ENTRY_WIDTH:
+        priv->ip_entry_width = g_value_get_int(value);
+        break;
+    case PROP_METRIC_ENTRY_WIDTH:
+        priv->metric_entry_width = g_value_get_int(value);
+        break;
+    case PROP_ROUTE:
+        if (priv->route)
+            nm_ip_route_unref(priv->route);
+        priv->route = g_value_dup_boxed(value);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nmt_route_entry_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+    NmtRouteEntryPrivate *priv = NMT_ROUTE_ENTRY_GET_PRIVATE(object);
+
+    switch (prop_id) {
+    case PROP_FAMILY:
+        g_value_set_int(value, priv->family);
+        break;
+    case PROP_IP_ENTRY_WIDTH:
+        g_value_set_int(value, priv->ip_entry_width);
+        break;
+    case PROP_METRIC_ENTRY_WIDTH:
+        g_value_set_int(value, priv->metric_entry_width);
+        break;
+    case PROP_ROUTE:
+        g_value_set_boxed(value, priv->route);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nmt_route_entry_class_init(NmtRouteEntryClass *entry_class)
+{
+    GObjectClass *      object_class = G_OBJECT_CLASS(entry_class);
+    NmtNewtWidgetClass *widget_class = NMT_NEWT_WIDGET_CLASS(entry_class);
+
+    g_type_class_add_private(entry_class, sizeof(NmtRouteEntryPrivate));
+
+    /* virtual methods */
+    object_class->constructed  = nmt_route_entry_constructed;
+    object_class->set_property = nmt_route_entry_set_property;
+    object_class->get_property = nmt_route_entry_get_property;
+    object_class->finalize     = nmt_route_entry_finalize;
+
+    widget_class->get_focus_component = nmt_route_entry_get_focus_component;
+
+    /**
+     * NmtRouteEntry:family:
+     *
+     * The address family of the route, eg, %AF_INET
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_FAMILY,
+        g_param_spec_int("family",
+                         "",
+                         "",
+                         0,
+                         G_MAXINT,
+                         0,
+                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+    /**
+     * NmtRouteEntry:ip-entry-width:
+     *
+     * The width in characters of the IP address entries
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_IP_ENTRY_WIDTH,
+        g_param_spec_int("ip-entry-width",
+                         "",
+                         "",
+                         0,
+                         G_MAXINT,
+                         0,
+                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+    /**
+     * NmtRouteEntry:metric-entry-width:
+     *
+     * The width in characters of the metric entry
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_METRIC_ENTRY_WIDTH,
+        g_param_spec_int("metric-entry-width",
+                         "",
+                         "",
+                         0,
+                         G_MAXINT,
+                         0,
+                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+    /**
+     * NmtRouteEntry:route:
+     *
+     * The contents of the entries, as an #NMIPRoute.
+     */
+    g_object_class_install_property(object_class,
+                                    PROP_ROUTE,
+                                    g_param_spec_boxed("route",
+                                                       "",
+                                                       "",
+                                                       nm_ip_route_get_type(),
+                                                       G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+}
diff --git a/src/nmtui/nmt-route-entry.h b/src/nmtui/nmt-route-entry.h
new file mode 100644
index 00000000..102cb61c
--- /dev/null
+++ b/src/nmtui/nmt-route-entry.h
@@ -0,0 +1,35 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+#ifndef NMT_ROUTE_ENTRY_H
+#define NMT_ROUTE_ENTRY_H
+
+#include "libnmt-newt/nmt-newt.h"
+
+#define NMT_TYPE_ROUTE_ENTRY (nmt_route_entry_get_type())
+#define NMT_ROUTE_ENTRY(obj) \
+    (G_TYPE_CHECK_INSTANCE_CAST((obj), NMT_TYPE_ROUTE_ENTRY, NmtRouteEntry))
+#define NMT_ROUTE_ENTRY_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NMT_TYPE_ROUTE_ENTRY, NmtRouteEntryClass))
+#define NMT_IS_ROUTE_ENTRY(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMT_TYPE_ROUTE_ENTRY))
+#define NMT_IS_ROUTE_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NMT_TYPE_ROUTE_ENTRY))
+#define NMT_ROUTE_ENTRY_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NMT_TYPE_ROUTE_ENTRY, NmtRouteEntryClass))
+
+typedef struct {
+    NmtNewtGrid parent;
+
+} NmtRouteEntry;
+
+typedef struct {
+    NmtNewtGridClass parent;
+
+} NmtRouteEntryClass;
+
+GType nmt_route_entry_get_type(void);
+
+NmtNewtWidget *nmt_route_entry_new(int family, int ip_entry_width, int metric_entry_width);
+
+#endif /* NMT_ROUTE_ENTRY_H */
diff --git a/src/nmtui/nmt-route-table.c b/src/nmtui/nmt-route-table.c
new file mode 100644
index 00000000..fcb14e6a
--- /dev/null
+++ b/src/nmtui/nmt-route-table.c
@@ -0,0 +1,304 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+/**
+ * SECTION:nmt-route-table
+ * @short_description: An editable list of IP4 or IP6 routes
+ *
+ * #NmtRouteTable implements a list of #NmtRouteEntry, plus headers,
+ * and buttons to add and remove entries.
+ */
+
+#include "libnm-client-aux-extern/nm-default-client.h"
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <stdlib.h>
+
+#include "nmt-route-table.h"
+#include "nmt-route-entry.h"
+#include "nmt-widget-list.h"
+
+G_DEFINE_TYPE(NmtRouteTable, nmt_route_table, NMT_TYPE_NEWT_GRID)
+
+#define NMT_ROUTE_TABLE_GET_PRIVATE(o) \
+    (G_TYPE_INSTANCE_GET_PRIVATE((o), NMT_TYPE_ROUTE_TABLE, NmtRouteTablePrivate))
+
+typedef struct {
+    int family;
+
+    int ip_entry_width;
+    int metric_entry_width;
+
+    GPtrArray *    routes;
+    NmtNewtWidget *list;
+} NmtRouteTablePrivate;
+
+enum {
+    PROP_0,
+    PROP_FAMILY,
+    PROP_ROUTES,
+
+    LAST_PROP
+};
+
+/**
+ * nmt_route_table_new:
+ * @family: the address family, eg %AF_INET
+ *
+ * Creates a new #NmtRouteTable
+ *
+ * Returns: a new #NmtRouteTable
+ */
+NmtNewtWidget *
+nmt_route_table_new(int family)
+{
+    return g_object_new(NMT_TYPE_ROUTE_TABLE, "family", family, NULL);
+}
+
+static gboolean
+route_list_transform_to_route(GBinding *    binding,
+                              const GValue *source_value,
+                              GValue *      target_value,
+                              gpointer      user_data)
+{
+    NmtRouteTable *       table = NMT_ROUTE_TABLE(g_binding_get_source(binding));
+    NmtRouteTablePrivate *priv  = NMT_ROUTE_TABLE_GET_PRIVATE(table);
+    int                   n     = GPOINTER_TO_INT(user_data);
+    NMIPRoute *           route;
+
+    if (n >= priv->routes->len)
+        return FALSE;
+
+    route = priv->routes->pdata[n];
+    g_value_set_boxed(target_value, route);
+    return TRUE;
+}
+
+static gboolean
+route_list_transform_from_route(GBinding *    binding,
+                                const GValue *source_value,
+                                GValue *      target_value,
+                                gpointer      user_data)
+{
+    NmtRouteTable *       table = NMT_ROUTE_TABLE(g_binding_get_source(binding));
+    NmtRouteTablePrivate *priv  = NMT_ROUTE_TABLE_GET_PRIVATE(table);
+    int                   n     = GPOINTER_TO_INT(user_data);
+    GPtrArray *           routes;
+    NMIPRoute *           route;
+
+    if (n >= priv->routes->len)
+        return FALSE;
+    route = priv->routes->pdata[n];
+
+    routes       = priv->routes;
+    priv->routes = g_ptr_array_new_with_free_func((GDestroyNotify) nm_ip_route_unref);
+
+    if (route)
+        nm_ip_route_unref(route);
+    routes->pdata[n] = g_value_dup_boxed(source_value);
+
+    g_value_take_boxed(target_value, routes);
+    return TRUE;
+}
+
+static NmtNewtWidget *
+create_route_entry(NmtWidgetList *list, int num, gpointer table)
+{
+    NmtRouteTablePrivate *priv = NMT_ROUTE_TABLE_GET_PRIVATE(table);
+    NmtNewtWidget *       entry;
+
+    entry = nmt_route_entry_new(priv->family, priv->ip_entry_width, priv->metric_entry_width);
+
+    g_object_bind_property_full(table,
+                                "routes",
+                                entry,
+                                "route",
+                                G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE,
+                                route_list_transform_to_route,
+                                route_list_transform_from_route,
+                                GINT_TO_POINTER(num),
+                                NULL);
+    return entry;
+}
+
+static void
+add_route(NmtWidgetList *list, gpointer table)
+{
+    NmtRouteTablePrivate *priv = NMT_ROUTE_TABLE_GET_PRIVATE(table);
+    NMIPRoute *           route;
+
+    if (priv->family == AF_INET)
+        route = nm_ip_route_new(AF_INET, "0.0.0.0", 32, NULL, -1, NULL);
+    else
+        route = nm_ip_route_new(AF_INET6, "::", 128, NULL, -1, NULL);
+    g_ptr_array_add(priv->routes, route);
+    nmt_widget_list_set_length(list, priv->routes->len);
+    g_object_notify(table, "routes");
+}
+
+static void
+remove_route(NmtWidgetList *list, int num, gpointer table)
+{
+    NmtRouteTablePrivate *priv = NMT_ROUTE_TABLE_GET_PRIVATE(table);
+
+    if (num >= priv->routes->len)
+        return;
+
+    g_ptr_array_remove_index(priv->routes, num);
+    nmt_widget_list_set_length(list, priv->routes->len);
+
+    g_object_notify(table, "routes");
+}
+
+static void
+nmt_route_table_init(NmtRouteTable *table)
+{
+    NmtRouteTablePrivate *priv = NMT_ROUTE_TABLE_GET_PRIVATE(table);
+    NmtNewtWidget *       header, *empty;
+    NmtNewtWidget *       dest_prefix_label, *next_hop_label, *metric_label;
+    int                   dest_prefix_width, next_hop_width, metric_width;
+    char *                text;
+
+    priv->routes = g_ptr_array_new_with_free_func((GDestroyNotify) nm_ip_route_unref);
+
+    header = nmt_newt_grid_new();
+
+    text              = g_strdup_printf("%s/%s", _("Destination"), _("Prefix"));
+    dest_prefix_width = nmt_newt_text_width(text);
+    dest_prefix_label =
+        g_object_new(NMT_TYPE_NEWT_LABEL, "text", text, "style", NMT_NEWT_LABEL_PLAIN, NULL);
+    g_free(text);
+    nmt_newt_grid_add(NMT_NEWT_GRID(header), dest_prefix_label, 0, 0);
+
+    text = _("Next Hop");
+    next_hop_label =
+        g_object_new(NMT_TYPE_NEWT_LABEL, "text", text, "style", NMT_NEWT_LABEL_PLAIN, NULL);
+    next_hop_width = nmt_newt_text_width(text);
+    nmt_newt_grid_add(NMT_NEWT_GRID(header), next_hop_label, 1, 0);
+
+    text = _("Metric");
+    metric_label =
+        g_object_new(NMT_TYPE_NEWT_LABEL, "text", text, "style", NMT_NEWT_LABEL_PLAIN, NULL);
+    metric_width = nmt_newt_text_width(text);
+    nmt_newt_grid_add(NMT_NEWT_GRID(header), metric_label, 2, 0);
+
+    priv->ip_entry_width     = MAX(20, MAX(dest_prefix_width, next_hop_width));
+    priv->metric_entry_width = MAX(7, metric_width);
+
+    nmt_newt_widget_set_padding(dest_prefix_label,
+                                0,
+                                0,
+                                priv->ip_entry_width - dest_prefix_width,
+                                0);
+    nmt_newt_widget_set_padding(next_hop_label, 2, 0, priv->ip_entry_width - next_hop_width, 0);
+    nmt_newt_widget_set_padding(metric_label, 2, 0, priv->metric_entry_width - metric_width, 0);
+
+    nmt_newt_grid_add(NMT_NEWT_GRID(table), header, 0, 0);
+
+    empty      = nmt_newt_label_new(_("No custom routes are defined."));
+    priv->list = nmt_widget_list_new(create_route_entry, table, NULL, empty);
+    g_signal_connect(priv->list, "add-clicked", G_CALLBACK(add_route), table);
+    g_signal_connect(priv->list, "remove-clicked", G_CALLBACK(remove_route), table);
+    nmt_newt_grid_add(NMT_NEWT_GRID(table), priv->list, 0, 1);
+}
+
+static void
+nmt_route_table_finalize(GObject *object)
+{
+    NmtRouteTablePrivate *priv = NMT_ROUTE_TABLE_GET_PRIVATE(object);
+
+    g_ptr_array_unref(priv->routes);
+
+    G_OBJECT_CLASS(nmt_route_table_parent_class)->finalize(object);
+}
+
+static void
+nmt_route_table_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+    NmtRouteTablePrivate *priv = NMT_ROUTE_TABLE_GET_PRIVATE(object);
+    GPtrArray *           array;
+    int                   i;
+
+    switch (prop_id) {
+    case PROP_FAMILY:
+        priv->family = g_value_get_int(value);
+        break;
+    case PROP_ROUTES:
+        array = g_value_get_boxed(value);
+        g_ptr_array_set_size(priv->routes, 0);
+        for (i = 0; i < array->len; i++) {
+            nm_ip_route_ref(array->pdata[i]);
+            g_ptr_array_add(priv->routes, array->pdata[i]);
+        }
+        nmt_widget_list_set_length(NMT_WIDGET_LIST(priv->list), priv->routes->len);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nmt_route_table_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+    NmtRouteTablePrivate *priv = NMT_ROUTE_TABLE_GET_PRIVATE(object);
+
+    switch (prop_id) {
+    case PROP_FAMILY:
+        g_value_set_int(value, priv->family);
+        break;
+    case PROP_ROUTES:
+        g_value_set_boxed(value, priv->routes);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nmt_route_table_class_init(NmtRouteTableClass *table_class)
+{
+    GObjectClass *object_class = G_OBJECT_CLASS(table_class);
+
+    g_type_class_add_private(table_class, sizeof(NmtRouteTablePrivate));
+
+    /* virtual methods */
+    object_class->set_property = nmt_route_table_set_property;
+    object_class->get_property = nmt_route_table_get_property;
+    object_class->finalize     = nmt_route_table_finalize;
+
+    /**
+     * NmtRouteTable:family:
+     *
+     * The network address family of the routes, eg %AF_INET
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_FAMILY,
+        g_param_spec_int("family",
+                         "",
+                         "",
+                         -1,
+                         G_MAXINT,
+                         -1,
+                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+    /**
+     * NmtRouteTable:routes:
+     *
+     * The array of routes, suitable for binding to #NMSettingIP4Config:routes
+     * or #NMSettingIP6Config:routes.
+     *
+     * Element-type: NMIPRoute
+     */
+    g_object_class_install_property(object_class,
+                                    PROP_ROUTES,
+                                    g_param_spec_boxed("routes",
+                                                       "",
+                                                       "",
+                                                       G_TYPE_PTR_ARRAY,
+                                                       G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+}
diff --git a/src/nmtui/nmt-route-table.h b/src/nmtui/nmt-route-table.h
new file mode 100644
index 00000000..fb29fd33
--- /dev/null
+++ b/src/nmtui/nmt-route-table.h
@@ -0,0 +1,35 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+#ifndef NMT_ROUTE_TABLE_H
+#define NMT_ROUTE_TABLE_H
+
+#include "libnmt-newt/nmt-newt.h"
+
+#define NMT_TYPE_ROUTE_TABLE (nmt_route_table_get_type())
+#define NMT_ROUTE_TABLE(obj) \
+    (G_TYPE_CHECK_INSTANCE_CAST((obj), NMT_TYPE_ROUTE_TABLE, NmtRouteTable))
+#define NMT_ROUTE_TABLE_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NMT_TYPE_ROUTE_TABLE, NmtRouteTableClass))
+#define NMT_IS_ROUTE_TABLE(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMT_TYPE_ROUTE_TABLE))
+#define NMT_IS_ROUTE_TABLE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NMT_TYPE_ROUTE_TABLE))
+#define NMT_ROUTE_TABLE_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NMT_TYPE_ROUTE_TABLE, NmtRouteTableClass))
+
+typedef struct {
+    NmtNewtGrid parent;
+
+} NmtRouteTable;
+
+typedef struct {
+    NmtNewtGridClass parent;
+
+} NmtRouteTableClass;
+
+GType nmt_route_table_get_type(void);
+
+NmtNewtWidget *nmt_route_table_new(int family);
+
+#endif /* NMT_ROUTE_TABLE_H */
diff --git a/src/nmtui/nmt-slave-list.c b/src/nmtui/nmt-slave-list.c
new file mode 100644
index 00000000..a6ce554d
--- /dev/null
+++ b/src/nmtui/nmt-slave-list.c
@@ -0,0 +1,253 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+/**
+ * SECTION:nmt-slave-list:
+ * @short_description: An editable list of a connection's slaves
+ *
+ * #NmtSlaveList implements an #NmtEditConnectionList for the
+ * slaves of a connection.
+ */
+
+#include "libnm-client-aux-extern/nm-default-client.h"
+
+#include "nmt-slave-list.h"
+
+G_DEFINE_TYPE(NmtSlaveList, nmt_slave_list, NMT_TYPE_EDIT_CONNECTION_LIST)
+
+#define NMT_SLAVE_LIST_GET_PRIVATE(o) \
+    (G_TYPE_INSTANCE_GET_PRIVATE((o), NMT_TYPE_SLAVE_LIST, NmtSlaveListPrivate))
+
+typedef struct {
+    NMConnection *master;
+    const char *  master_type, *master_uuid;
+
+    NmtAddConnectionTypeFilter type_filter;
+    gpointer                   type_filter_data;
+} NmtSlaveListPrivate;
+
+enum {
+    PROP_0,
+    PROP_MASTER,
+    PROP_TYPE_FILTER,
+    PROP_TYPE_FILTER_DATA,
+
+    LAST_PROP
+};
+
+static gboolean nmt_slave_list_connection_filter(NmtEditConnectionList *list,
+                                                 NMConnection *         connection,
+                                                 gpointer               user_data);
+
+/**
+ * nmt_slave_list_new:
+ * @master: the master #NMConnection whose slaves are being listed
+ * @type_filter: (allow-none): a function to limit the available slave types
+ * @type_filter_data: (allow-none): data for @type_filter.
+ *
+ * Creates a new #NmtSlaveList.
+ *
+ * If @type_filter is non-%NULL, it will be used to limit the connection
+ * types that are available when the user clicks on the "Add" button to add
+ * a new slave. If the @type_filter filters the list down to only a single
+ * connection type, then the user will not be presented with a connection-type
+ * dialog, and will instead be immediately taken to an editor window for the
+ * new slave after clicking "Add".
+ *
+ * Returns: a new #NmtSlaveList.
+ */
+NmtNewtWidget *
+nmt_slave_list_new(NMConnection *             master,
+                   NmtAddConnectionTypeFilter type_filter,
+                   gpointer                   type_filter_data)
+{
+    return g_object_new(NMT_TYPE_SLAVE_LIST,
+                        "master",
+                        master,
+                        "type-filter",
+                        type_filter,
+                        "type-filter-data",
+                        type_filter_data,
+                        "grouped",
+                        FALSE,
+                        "connection-filter",
+                        nmt_slave_list_connection_filter,
+                        NULL);
+}
+
+static void
+nmt_slave_list_init(NmtSlaveList *list)
+{}
+
+static void
+nmt_slave_list_finalize(GObject *object)
+{
+    NmtSlaveListPrivate *priv = NMT_SLAVE_LIST_GET_PRIVATE(object);
+
+    g_object_unref(priv->master);
+
+    G_OBJECT_CLASS(nmt_slave_list_parent_class)->finalize(object);
+}
+
+static gboolean
+nmt_slave_list_connection_filter(NmtEditConnectionList *list,
+                                 NMConnection *         connection,
+                                 gpointer               user_data)
+{
+    NmtSlaveListPrivate *priv = NMT_SLAVE_LIST_GET_PRIVATE(list);
+    NMSettingConnection *s_con;
+    const char *         master, *master_ifname, *slave_type;
+
+    s_con = nm_connection_get_setting_connection(connection);
+    g_return_val_if_fail(s_con != NULL, FALSE);
+
+    slave_type = nm_setting_connection_get_slave_type(s_con);
+    if (g_strcmp0(slave_type, priv->master_type) != 0)
+        return FALSE;
+
+    master = nm_setting_connection_get_master(s_con);
+    if (!master)
+        return FALSE;
+
+    master_ifname = nm_connection_get_interface_name(priv->master);
+    if (g_strcmp0(master, master_ifname) != 0 && g_strcmp0(master, priv->master_uuid) != 0)
+        return FALSE;
+
+    return TRUE;
+}
+
+static void
+nmt_slave_list_add_connection(NmtEditConnectionList *list)
+{
+    NmtSlaveListPrivate *priv = NMT_SLAVE_LIST_GET_PRIVATE(list);
+
+    nmt_add_connection_full(_("Select the type of slave connection you wish to add."),
+                            NULL,
+                            priv->master,
+                            priv->type_filter,
+                            priv->type_filter_data);
+}
+
+static void
+nmt_slave_list_edit_connection(NmtEditConnectionList *list, NMConnection *connection)
+{
+    nmt_edit_connection(connection);
+}
+
+static void
+nmt_slave_list_remove_connection(NmtEditConnectionList *list, NMRemoteConnection *connection)
+{
+    nmt_remove_connection(connection);
+}
+
+static void
+nmt_slave_list_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+    NmtSlaveListPrivate *priv = NMT_SLAVE_LIST_GET_PRIVATE(object);
+
+    switch (prop_id) {
+    case PROP_MASTER:
+        priv->master = g_value_dup_object(value);
+        if (priv->master) {
+            NMSettingConnection *s_con = nm_connection_get_setting_connection(priv->master);
+
+            priv->master_type = nm_setting_connection_get_connection_type(s_con);
+            priv->master_uuid = nm_setting_connection_get_uuid(s_con);
+        }
+        break;
+    case PROP_TYPE_FILTER:
+        priv->type_filter = g_value_get_pointer(value);
+        break;
+    case PROP_TYPE_FILTER_DATA:
+        priv->type_filter_data = g_value_get_pointer(value);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nmt_slave_list_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+    NmtSlaveListPrivate *priv = NMT_SLAVE_LIST_GET_PRIVATE(object);
+
+    switch (prop_id) {
+    case PROP_MASTER:
+        g_value_set_object(value, priv->master);
+        break;
+    case PROP_TYPE_FILTER:
+        g_value_set_pointer(value, priv->type_filter);
+        break;
+    case PROP_TYPE_FILTER_DATA:
+        g_value_set_pointer(value, priv->type_filter_data);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nmt_slave_list_class_init(NmtSlaveListClass *list_class)
+{
+    GObjectClass *              object_class          = G_OBJECT_CLASS(list_class);
+    NmtEditConnectionListClass *connection_list_class = NMT_EDIT_CONNECTION_LIST_CLASS(list_class);
+
+    g_type_class_add_private(list_class, sizeof(NmtSlaveListPrivate));
+
+    /* virtual methods */
+    object_class->set_property = nmt_slave_list_set_property;
+    object_class->get_property = nmt_slave_list_get_property;
+    object_class->finalize     = nmt_slave_list_finalize;
+
+    connection_list_class->add_connection    = nmt_slave_list_add_connection;
+    connection_list_class->edit_connection   = nmt_slave_list_edit_connection;
+    connection_list_class->remove_connection = nmt_slave_list_remove_connection;
+
+    /**
+     * NmtSlaveList:master:
+     *
+     * The master #NMConnection whose slaves are being displayed.
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_MASTER,
+        g_param_spec_object("master",
+                            "",
+                            "",
+                            NM_TYPE_CONNECTION,
+                            G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+    /**
+     * NmtSlaveList:type-filter:
+     *
+     * If non-%NULL, this will be used to limit the connection types
+     * that are available when the user clicks on the "Add" button to
+     * add a new slave. If the filter filters the list down to only a
+     * single connection type, then the user will not be presented
+     * with a connection-type dialog, and will instead be immediately
+     * taken to an editor window for the new slave after clicking
+     * "Add".
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_TYPE_FILTER,
+        g_param_spec_pointer("type-filter",
+                             "",
+                             "",
+                             G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+    /**
+     * NmtSlaveList:type-filter-data:
+     *
+     * User data passed to #NmtSlaveList:type-filter
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_TYPE_FILTER_DATA,
+        g_param_spec_pointer("type-filter-data",
+                             "",
+                             "",
+                             G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+}
diff --git a/src/nmtui/nmt-slave-list.h b/src/nmtui/nmt-slave-list.h
new file mode 100644
index 00000000..f220fc12
--- /dev/null
+++ b/src/nmtui/nmt-slave-list.h
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+#ifndef NMT_SLAVE_LIST_H
+#define NMT_SLAVE_LIST_H
+
+#include "nmt-edit-connection-list.h"
+#include "nmtui-edit.h"
+
+#define NMT_TYPE_SLAVE_LIST (nmt_slave_list_get_type())
+#define NMT_SLAVE_LIST(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NMT_TYPE_SLAVE_LIST, NmtSlaveList))
+#define NMT_SLAVE_LIST_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NMT_TYPE_SLAVE_LIST, NmtSlaveListClass))
+#define NMT_IS_SLAVE_LIST(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMT_TYPE_SLAVE_LIST))
+#define NMT_IS_SLAVE_LIST_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NMT_TYPE_SLAVE_LIST))
+#define NMT_SLAVE_LIST_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NMT_TYPE_SLAVE_LIST, NmtSlaveListClass))
+
+typedef struct {
+    NmtEditConnectionList parent;
+
+} NmtSlaveList;
+
+typedef struct {
+    NmtEditConnectionListClass parent;
+
+} NmtSlaveListClass;
+
+GType nmt_slave_list_get_type(void);
+
+NmtNewtWidget *nmt_slave_list_new(NMConnection *             master,
+                                  NmtAddConnectionTypeFilter type_filter,
+                                  gpointer                   type_filter_data);
+
+#endif /* NMT_SLAVE_LIST_H */
diff --git a/src/nmtui/nmt-utils.c b/src/nmtui/nmt-utils.c
new file mode 100644
index 00000000..bac763f6
--- /dev/null
+++ b/src/nmtui/nmt-utils.c
@@ -0,0 +1,130 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+/**
+ * SECTION:nmt-utils
+ * @short_description: Miscellaneous nmtui-specific utilities
+ */
+
+#include "libnm-client-aux-extern/nm-default-client.h"
+
+#include "nmt-utils.h"
+
+/**
+ * NmtSyncOp:
+ *
+ * A helper object used when synchronously waiting for an asynchronous
+ * operation to complete.
+ *
+ * The caller first does:
+ *
+ * |[
+ *     NmtSyncOp op;
+ *
+ *     nmt_sync_op_init (&op);
+ * ]|
+ *
+ * It then passes the op as the user_data to the async operation's
+ * callback function, and then calls nmt_sync_op_wait_boolean() or
+ * nmt_sync_op_wait_pointer() to wait for the result.
+ *
+ * When the async callback is invoked, it should call
+ * nmt_sync_op_complete_boolean() or nmt_sync_op_complete_pointer() to
+ * return a result or an error to the caller.
+ *
+ * There is no free/clear function; any memory that needs to be freed
+ * will have been returned to the caller from
+ * nmt_sync_op_wait_boolean() or nmt_sync_op_wait_pointer(), so there
+ * is nothing left that needs to be freed.
+ */
+
+typedef struct {
+    gpointer result;
+    GError * error;
+    gpointer complete;
+} NmtSyncOpReal;
+
+/**
+ * nmt_sync_op_init:
+ * @op: pointer to a stack-allocated #NmtSyncOp
+ *
+ * Initializes @op before use.
+ */
+void
+nmt_sync_op_init(NmtSyncOp *op)
+{
+    memset(op, 0, sizeof(*op));
+}
+
+/**
+ * nmt_sync_op_wait_boolean:
+ * @op: the #NmtSyncOp
+ * @error: return location for a #GError
+ *
+ * This runs the main loop until @op's operation returns, and then
+ * returns the result or error.
+ *
+ * Returns: the result of the operation.
+ */
+gboolean
+nmt_sync_op_wait_boolean(NmtSyncOp *op, GError **error)
+{
+    return GPOINTER_TO_UINT(nmt_sync_op_wait_pointer(op, error));
+}
+
+/**
+ * nmt_sync_op_complete_boolean:
+ * @op: the #NmtSyncOp
+ * @result: the result of the operation
+ * @error: (allow-none): the error, or %NULL
+ *
+ * Completes @op and returns @result and/or @error to the caller.
+ */
+void
+nmt_sync_op_complete_boolean(NmtSyncOp *op, gboolean result, GError *error)
+{
+    nmt_sync_op_complete_pointer(op, GUINT_TO_POINTER(result), error);
+}
+
+/**
+ * nmt_sync_op_wait_pointer:
+ * @op: the #NmtSyncOp
+ * @error: return location for a #GError
+ *
+ * This runs the main loop until @op's operation returns, and then
+ * returns the result or error.
+ *
+ * Returns: the result of the operation.
+ */
+gpointer
+nmt_sync_op_wait_pointer(NmtSyncOp *op, GError **error)
+{
+    NmtSyncOpReal *real = (NmtSyncOpReal *) op;
+
+    while (!real->complete)
+        g_main_context_iteration(NULL, TRUE);
+
+    if (real->error)
+        g_propagate_error(error, real->error);
+    return real->result;
+}
+
+/**
+ * nmt_sync_op_complete_pointer:
+ * @op: the #NmtSyncOp
+ * @result: the result of the operation
+ * @error: (allow-none): the error, or %NULL
+ *
+ * Completes @op and returns @result and/or @error to the caller.
+ */
+void
+nmt_sync_op_complete_pointer(NmtSyncOp *op, gpointer result, GError *error)
+{
+    NmtSyncOpReal *real = (NmtSyncOpReal *) op;
+
+    real->result   = result;
+    real->error    = error ? g_error_copy(error) : NULL;
+    real->complete = GUINT_TO_POINTER(TRUE);
+}
diff --git a/src/nmtui/nmt-utils.h b/src/nmtui/nmt-utils.h
new file mode 100644
index 00000000..3b780ae7
--- /dev/null
+++ b/src/nmtui/nmt-utils.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+#ifndef NMT_UTILS_H
+#define NMT_UTILS_H
+
+typedef struct {
+    gpointer private[3];
+} NmtSyncOp;
+
+void nmt_sync_op_init(NmtSyncOp *op);
+
+gboolean nmt_sync_op_wait_boolean(NmtSyncOp *op, GError **error);
+void     nmt_sync_op_complete_boolean(NmtSyncOp *op, gboolean result, GError *error);
+
+gpointer nmt_sync_op_wait_pointer(NmtSyncOp *op, GError **error);
+void     nmt_sync_op_complete_pointer(NmtSyncOp *op, gpointer result, GError *error);
+
+#endif /* NMT_UTILS_H */
diff --git a/src/nmtui/nmt-widget-list.c b/src/nmtui/nmt-widget-list.c
new file mode 100644
index 00000000..cd0c6c3c
--- /dev/null
+++ b/src/nmtui/nmt-widget-list.c
@@ -0,0 +1,487 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+/**
+ * SECTION:nmt-widget-list
+ * @short_description: A list of widgets, with Add and Remove buttons
+ *
+ * #NmtWidgetList presents a homogeneous list of widgets, with "Remove"
+ * buttons next to each one, and an "Add" button at the button to add
+ * new ones.
+ *
+ * It is the base class for #NmtAddressList, and is used internally by
+ * #NmtRouteTable.
+ *
+ * FIXME: The way this works is sort of weird.
+ */
+
+#include "libnm-client-aux-extern/nm-default-client.h"
+
+#include "nmt-widget-list.h"
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <stdlib.h>
+
+#include "libnmt-newt/nmt-newt.h"
+
+G_DEFINE_TYPE(NmtWidgetList, nmt_widget_list, NMT_TYPE_NEWT_GRID)
+
+#define NMT_WIDGET_LIST_GET_PRIVATE(o) \
+    (G_TYPE_INSTANCE_GET_PRIVATE((o), NMT_TYPE_WIDGET_LIST, NmtWidgetListPrivate))
+
+typedef struct {
+    int length;
+
+    NmtWidgetListCallback create_callback;
+    gpointer              user_data;
+    GDestroyNotify        destroy_notify;
+
+    NmtNewtWidget *empty_widget;
+
+    GPtrArray *widgets;
+    GPtrArray *remove_buttons;
+
+    NmtNewtWidget *add_button;
+    GBinding *     add_sensitivity;
+} NmtWidgetListPrivate;
+
+enum {
+    PROP_0,
+    PROP_CREATE_CALLBACK,
+    PROP_USER_DATA,
+    PROP_DESTROY_NOTIFY,
+    PROP_EMPTY_WIDGET,
+    PROP_LENGTH,
+
+    LAST_PROP
+};
+
+enum {
+    ADD_CLICKED,
+    REMOVE_CLICKED,
+
+    LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = {0};
+
+static void add_clicked(NmtNewtButton *button, gpointer user_data);
+static void remove_clicked(NmtNewtButton *button, gpointer user_data);
+
+/**
+ * NmtWidgetListCallback:
+ * @list: the #NmtWidgetList
+ * @n: the number of the widget being added
+ * @user_data: the callback's user data
+ *
+ * Called by #NmtWidgetList to ask for a new widget to be created.
+ *
+ * Note that the widget is not created to go with any particular
+ * value, but rather is created to be at a certain spot in the list.
+ * When an element is deleted from the list, it is actually always
+ * the last widget in the list that is removed, but it is assumed
+ * that the widget list is bound to some array-valued property, and
+ * so when an element is deleted from that array, the widgets will
+ * all update themselves automatically when the array changes.
+ *
+ * Returns: a new widget for the list
+ */
+
+/**
+ * nmt_widget_list_new:
+ * @create_callback: callback to create new widgets
+ * @user_data: user data for @create_callback
+ * @destroy_notify: #GDestroyNotify for @user_data
+ * @empty_widget: (allow-none): a widget to display when there are
+ *   no "real" widgets in the list.
+ *
+ * Creates a new #NmtWidgetList.
+ *
+ * Returns: a new #NmtWidgetList.
+ */
+NmtNewtWidget *
+nmt_widget_list_new(NmtWidgetListCallback create_callback,
+                    gpointer              user_data,
+                    GDestroyNotify        destroy_notify,
+                    NmtNewtWidget *       empty_widget)
+{
+    return g_object_new(NMT_TYPE_WIDGET_LIST,
+                        "create-callback",
+                        create_callback,
+                        "user-data",
+                        user_data,
+                        "destroy-notify",
+                        destroy_notify,
+                        "empty-widget",
+                        empty_widget,
+                        NULL);
+}
+
+static void
+nmt_widget_list_init(NmtWidgetList *list)
+{
+    NmtWidgetListPrivate *priv = NMT_WIDGET_LIST_GET_PRIVATE(list);
+
+    priv->widgets        = g_ptr_array_new();
+    priv->remove_buttons = g_ptr_array_new();
+
+    priv->add_button = nmt_newt_button_new(_("Add..."));
+    g_signal_connect(priv->add_button, "clicked", G_CALLBACK(add_clicked), list);
+    nmt_newt_grid_add(NMT_NEWT_GRID(list), priv->add_button, 0, 0);
+}
+
+static void
+nmt_widget_list_constructed(GObject *object)
+{
+    NmtWidgetListPrivate *priv = NMT_WIDGET_LIST_GET_PRIVATE(object);
+
+    if (priv->length == 0 && priv->empty_widget) {
+        nmt_newt_widget_set_visible(priv->empty_widget, TRUE);
+        nmt_newt_grid_move(NMT_NEWT_GRID(object), priv->add_button, 0, 1);
+    }
+
+    G_OBJECT_CLASS(nmt_widget_list_parent_class)->constructed(object);
+}
+
+static void
+nmt_widget_list_finalize(GObject *object)
+{
+    NmtWidgetListPrivate *priv = NMT_WIDGET_LIST_GET_PRIVATE(object);
+
+    g_ptr_array_unref(priv->widgets);
+    g_ptr_array_unref(priv->remove_buttons);
+
+    if (priv->user_data && priv->destroy_notify)
+        priv->destroy_notify(priv->user_data);
+
+    g_clear_object(&priv->empty_widget);
+
+    G_OBJECT_CLASS(nmt_widget_list_parent_class)->finalize(object);
+}
+
+static void
+ensure_widgets(NmtWidgetList *list)
+{
+    NmtWidgetListPrivate *priv = NMT_WIDGET_LIST_GET_PRIVATE(list);
+    NmtNewtWidget *       widget, *button, *focus;
+    gboolean              was_empty;
+    NmtNewtForm *         form;
+    int                   i;
+
+    was_empty = priv->widgets->len == 0;
+
+    if (priv->length < priv->widgets->len) {
+        /* remove excess widgets */
+        for (i = priv->length; i < priv->widgets->len; i++) {
+            nmt_newt_container_remove(NMT_NEWT_CONTAINER(list), priv->widgets->pdata[i]);
+            nmt_newt_container_remove(NMT_NEWT_CONTAINER(list), priv->remove_buttons->pdata[i]);
+        }
+        g_ptr_array_set_size(priv->widgets, priv->length);
+        g_ptr_array_set_size(priv->remove_buttons, priv->length);
+
+    } else if (priv->length > priv->widgets->len) {
+        /* add new widgets */
+        for (i = priv->widgets->len; i < priv->length; i++) {
+            widget = NMT_WIDGET_LIST_GET_CLASS(list)->create_widget(list, i);
+
+            nmt_newt_grid_add(NMT_NEWT_GRID(list), widget, 0, i);
+            g_ptr_array_add(priv->widgets, widget);
+
+            button = nmt_newt_button_new(_("Remove"));
+            g_signal_connect(button, "clicked", G_CALLBACK(remove_clicked), list);
+
+            nmt_newt_grid_add(NMT_NEWT_GRID(list), button, 1, i);
+            nmt_newt_widget_set_padding(button, 1, 0, 0, 0);
+            g_ptr_array_add(priv->remove_buttons, button);
+        }
+
+    } else
+        return;
+
+    if (priv->widgets->len == 0 && priv->empty_widget) {
+        nmt_newt_widget_set_visible(priv->empty_widget, TRUE);
+        nmt_newt_grid_move(NMT_NEWT_GRID(list), priv->add_button, 0, 1);
+    } else {
+        if (was_empty && priv->empty_widget)
+            nmt_newt_widget_set_visible(priv->empty_widget, FALSE);
+        nmt_newt_grid_move(NMT_NEWT_GRID(list), priv->add_button, 0, priv->length);
+    }
+
+    form = nmt_newt_widget_get_form(NMT_NEWT_WIDGET(list));
+    if (form) {
+        if (priv->widgets->len) {
+            if (was_empty)
+                focus = priv->widgets->pdata[0];
+            else
+                focus = priv->widgets->pdata[priv->widgets->len - 1];
+        } else
+            focus = priv->add_button;
+        nmt_newt_form_set_focus(form, focus);
+    }
+
+    g_clear_object(&priv->add_sensitivity);
+    if (priv->widgets->len) {
+        widget                = priv->widgets->pdata[priv->widgets->len - 1];
+        priv->add_sensitivity = g_object_bind_property(widget,
+                                                       "valid",
+                                                       priv->add_button,
+                                                       "sensitive",
+                                                       G_BINDING_SYNC_CREATE);
+        g_object_add_weak_pointer(G_OBJECT(priv->add_sensitivity),
+                                  (gpointer *) &priv->add_sensitivity);
+    } else
+        nmt_newt_component_set_sensitive(NMT_NEWT_COMPONENT(priv->add_button), TRUE);
+}
+
+static void
+add_clicked(NmtNewtButton *button, gpointer list)
+{
+    g_signal_emit(G_OBJECT(list), signals[ADD_CLICKED], 0, NULL);
+}
+
+static void
+remove_clicked(NmtNewtButton *button, gpointer list)
+{
+    NmtWidgetListPrivate *priv = NMT_WIDGET_LIST_GET_PRIVATE(list);
+    int                   i;
+
+    for (i = 0; i < priv->remove_buttons->len; i++) {
+        if (priv->remove_buttons->pdata[i] == (gpointer) button)
+            break;
+    }
+    g_return_if_fail(i < priv->remove_buttons->len);
+
+    g_signal_emit(G_OBJECT(list), signals[REMOVE_CLICKED], 0, i, NULL);
+}
+
+static NmtNewtWidget *
+nmt_widget_list_real_create_widget(NmtWidgetList *list, int n)
+{
+    NmtWidgetListPrivate *priv = NMT_WIDGET_LIST_GET_PRIVATE(list);
+
+    g_return_val_if_fail(priv->create_callback != NULL, NULL);
+
+    return priv->create_callback(list, n, priv->user_data);
+}
+
+/**
+ * nmt_widget_list_get_length:
+ * @list: the #NmtNewtWidgetList
+ *
+ * Gets the number of widgets in the list.
+ *
+ * Returns: the number of widgets in the list.
+ */
+int
+nmt_widget_list_get_length(NmtWidgetList *list)
+{
+    NmtWidgetListPrivate *priv = NMT_WIDGET_LIST_GET_PRIVATE(list);
+
+    return priv->length;
+}
+
+/**
+ * nmt_widget_list_set_length:
+ * @list: the #NmtNewtWidgetList
+ * @length: the new length
+ *
+ * Changes the number of widgets in the list. Widgets will be added or
+ * deleted as necessary.
+ */
+void
+nmt_widget_list_set_length(NmtWidgetList *list, int length)
+{
+    NmtWidgetListPrivate *priv = NMT_WIDGET_LIST_GET_PRIVATE(list);
+
+    if (priv->length != length) {
+        priv->length = length;
+        g_object_notify(G_OBJECT(list), "length");
+    }
+
+    ensure_widgets(list);
+}
+
+static void
+nmt_widget_list_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+    NmtWidgetListPrivate *priv = NMT_WIDGET_LIST_GET_PRIVATE(object);
+
+    switch (prop_id) {
+    case PROP_CREATE_CALLBACK:
+        priv->create_callback = g_value_get_pointer(value);
+        break;
+    case PROP_USER_DATA:
+        priv->user_data = g_value_get_pointer(value);
+        break;
+    case PROP_DESTROY_NOTIFY:
+        priv->destroy_notify = g_value_get_pointer(value);
+        break;
+    case PROP_LENGTH:
+        priv->length = g_value_get_int(value);
+        ensure_widgets(NMT_WIDGET_LIST(object));
+        break;
+    case PROP_EMPTY_WIDGET:
+        priv->empty_widget = g_value_get_object(value);
+        if (priv->empty_widget) {
+            g_object_ref_sink(priv->empty_widget);
+            nmt_newt_grid_add(NMT_NEWT_GRID(object), priv->empty_widget, 0, 0);
+        }
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nmt_widget_list_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+    NmtWidgetListPrivate *priv = NMT_WIDGET_LIST_GET_PRIVATE(object);
+
+    switch (prop_id) {
+    case PROP_CREATE_CALLBACK:
+        g_value_set_pointer(value, priv->create_callback);
+        break;
+    case PROP_USER_DATA:
+        g_value_set_pointer(value, priv->user_data);
+        break;
+    case PROP_DESTROY_NOTIFY:
+        g_value_set_pointer(value, priv->destroy_notify);
+        break;
+    case PROP_LENGTH:
+        g_value_set_int(value, priv->length);
+        break;
+    case PROP_EMPTY_WIDGET:
+        g_value_set_object(value, priv->empty_widget);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nmt_widget_list_class_init(NmtWidgetListClass *list_class)
+{
+    GObjectClass *object_class = G_OBJECT_CLASS(list_class);
+
+    g_type_class_add_private(list_class, sizeof(NmtWidgetListPrivate));
+
+    /* virtual methods */
+    object_class->constructed  = nmt_widget_list_constructed;
+    object_class->set_property = nmt_widget_list_set_property;
+    object_class->get_property = nmt_widget_list_get_property;
+    object_class->finalize     = nmt_widget_list_finalize;
+
+    list_class->create_widget = nmt_widget_list_real_create_widget;
+
+    /* signals */
+
+    /**
+     * NmtNewtWidget::add-clicked:
+     * @list: the #NmtNewtWidgetList
+     *
+     * Emitted when the user clicks the "Add" button. The caller can
+     * decide whether or not to add a new widget, and call
+     * nmt_widget_list_set_length() with the new length if so.
+     *
+     * FIXME: the "Add" button should be insensitive if it's
+     * not going to work.
+     */
+    signals[ADD_CLICKED] = g_signal_new("add-clicked",
+                                        G_OBJECT_CLASS_TYPE(object_class),
+                                        G_SIGNAL_RUN_FIRST,
+                                        G_STRUCT_OFFSET(NmtWidgetListClass, add_clicked),
+                                        NULL,
+                                        NULL,
+                                        NULL,
+                                        G_TYPE_NONE,
+                                        0);
+    /**
+     * NmtNewtWidget::remove-clicked:
+     * @list: the #NmtNewtWidgetList
+     * @n: the widget being removed
+     *
+     * Emitted when the user clicks one of the "Remove" buttons. The
+     * caller can decide whether or not to remove the widget, and
+     * call nmt_widget_list_set_length() with the new length if so.
+     *
+     * FIXME: the "Remove" button should be insensitive if it's not
+     * going to work.
+     */
+    signals[REMOVE_CLICKED] = g_signal_new("remove-clicked",
+                                           G_OBJECT_CLASS_TYPE(object_class),
+                                           G_SIGNAL_RUN_FIRST,
+                                           G_STRUCT_OFFSET(NmtWidgetListClass, remove_clicked),
+                                           NULL,
+                                           NULL,
+                                           NULL,
+                                           G_TYPE_NONE,
+                                           1,
+                                           G_TYPE_INT);
+
+    /* properties */
+
+    /**
+     * NmtWidgetList:create-callback:
+     *
+     * Callback called to create a new widget.
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_CREATE_CALLBACK,
+        g_param_spec_pointer("create-callback",
+                             "",
+                             "",
+                             G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+    /**
+     * NmtWidgetList:user-data:
+     *
+     * User data for #NmtWidgetList:create-callback
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_USER_DATA,
+        g_param_spec_pointer("user-data", "", "", G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+    /**
+     * NmtWidgetList:destroy-notify:
+     *
+     * #GDestroyNotify for #NmtWidgetList:user-data
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_DESTROY_NOTIFY,
+        g_param_spec_pointer("destroy-notify", "", "", G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+    /**
+     * NmtWidgetList:length:
+     *
+     * The length of the widget list; changing this value will add or
+     * remove widgets from the list.
+     */
+    g_object_class_install_property(object_class,
+                                    PROP_LENGTH,
+                                    g_param_spec_int("length",
+                                                     "",
+                                                     "",
+                                                     0,
+                                                     G_MAXINT,
+                                                     0,
+                                                     G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+    /**
+     * NmtWidgetList:empty-widget:
+     *
+     * If non-%NULL, this widget will be displayed when there are
+     * no "real" widgets in the list.
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_EMPTY_WIDGET,
+        g_param_spec_object("empty-widget",
+                            "",
+                            "",
+                            NMT_TYPE_NEWT_WIDGET,
+                            G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+}
diff --git a/src/nmtui/nmt-widget-list.h b/src/nmtui/nmt-widget-list.h
new file mode 100644
index 00000000..1825c884
--- /dev/null
+++ b/src/nmtui/nmt-widget-list.h
@@ -0,0 +1,50 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+#ifndef NMT_WIDGET_LIST_H
+#define NMT_WIDGET_LIST_H
+
+#include "libnmt-newt/nmt-newt-grid.h"
+
+#define NMT_TYPE_WIDGET_LIST (nmt_widget_list_get_type())
+#define NMT_WIDGET_LIST(obj) \
+    (G_TYPE_CHECK_INSTANCE_CAST((obj), NMT_TYPE_WIDGET_LIST, NmtWidgetList))
+#define NMT_WIDGET_LIST_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NMT_TYPE_WIDGET_LIST, NmtWidgetListClass))
+#define NMT_IS_WIDGET_LIST(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMT_TYPE_WIDGET_LIST))
+#define NMT_IS_WIDGET_LIST_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NMT_TYPE_WIDGET_LIST))
+#define NMT_WIDGET_LIST_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NMT_TYPE_WIDGET_LIST, NmtWidgetListClass))
+
+typedef struct {
+    NmtNewtGrid parent;
+
+} NmtWidgetList;
+
+typedef struct {
+    NmtNewtGridClass parent;
+
+    /* signals */
+    void (*add_clicked)(NmtWidgetList *list);
+    void (*remove_clicked)(NmtWidgetList *list, int n);
+
+    /* methods */
+    NmtNewtWidget *(*create_widget)(NmtWidgetList *list, int n);
+
+} NmtWidgetListClass;
+
+GType nmt_widget_list_get_type(void);
+
+typedef NmtNewtWidget *(*NmtWidgetListCallback)(NmtWidgetList *list, int n, gpointer user_data);
+
+NmtNewtWidget *nmt_widget_list_new(NmtWidgetListCallback create_callback,
+                                   gpointer              user_data,
+                                   GDestroyNotify        destroy_notify,
+                                   NmtNewtWidget *       empty_widget);
+
+int  nmt_widget_list_get_length(NmtWidgetList *list);
+void nmt_widget_list_set_length(NmtWidgetList *list, int length);
+
+#endif /* NMT_WIDGET_LIST_H */
diff --git a/src/nmtui/nmtui-connect.c b/src/nmtui/nmtui-connect.c
new file mode 100644
index 00000000..4aa91acb
--- /dev/null
+++ b/src/nmtui/nmtui-connect.c
@@ -0,0 +1,488 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+/**
+ * SECTION:nmtui-connect
+ * @short_description: nm-applet-like functionality
+ *
+ * nmtui-connect implements activating and deactivating #NMConnections,
+ * including presenting a password dialog if necessary.
+ */
+
+#include "libnm-client-aux-extern/nm-default-client.h"
+
+#include <stdlib.h>
+
+#include "libnmt-newt/nmt-newt.h"
+
+#include "nmtui.h"
+#include "nmtui-connect.h"
+#include "nmt-connect-connection-list.h"
+#include "nmt-password-dialog.h"
+#include "libnmc-base/nm-secret-agent-simple.h"
+#include "libnmc-base/nm-vpn-helpers.h"
+#include "libnmc-base/nm-client-utils.h"
+#include "nmt-utils.h"
+
+/**
+ * Runs openconnect to authenticate. The current screen state is saved
+ * before starting the command and restored after it returns.
+ */
+static gboolean
+openconnect_authenticate(NMConnection *connection, char **cookie, char **gateway, char **gwcert)
+{
+    GError *      error = NULL;
+    NMSettingVpn *s_vpn;
+    gboolean      ret;
+    int           status = 0;
+    const char *  gw, *port;
+
+    nmt_newt_message_dialog(
+        _("openconnect will be run to authenticate.\nIt will return to nmtui when completed."));
+
+    /* Get port */
+    s_vpn = nm_connection_get_setting_vpn(connection);
+    gw    = nm_setting_vpn_get_data_item(s_vpn, "gateway");
+    port  = gw ? strrchr(gw, ':') : NULL;
+
+    newtSuspend();
+
+    ret = nm_vpn_openconnect_authenticate_helper(gw, cookie, gateway, gwcert, &status, &error);
+
+    newtResume();
+
+    if (!ret) {
+        nmt_newt_message_dialog(_("Error: openconnect failed: %s"), error->message);
+        g_clear_error(&error);
+        return FALSE;
+    }
+
+    if (WIFEXITED(status)) {
+        if (WEXITSTATUS(status) != 0) {
+            nmt_newt_message_dialog(_("openconnect failed with status %d"), WEXITSTATUS(status));
+            return FALSE;
+        }
+    } else if (WIFSIGNALED(status)) {
+        nmt_newt_message_dialog(_("openconnect failed with signal %d"), WTERMSIG(status));
+        return FALSE;
+    }
+
+    if (gateway && *gateway && port) {
+        char *tmp = *gateway;
+        *gateway  = g_strdup_printf("%s%s", *gateway, port);
+        g_free(tmp);
+    }
+
+    return TRUE;
+}
+
+static void
+secrets_requested(NMSecretAgentSimple *agent,
+                  const char *         request_id,
+                  const char *         title,
+                  const char *         msg,
+                  GPtrArray *          secrets,
+                  gpointer             user_data)
+{
+    NmtNewtForm * form;
+    NMConnection *connection = NM_CONNECTION(user_data);
+    int           i;
+
+    /* Get secrets for OpenConnect VPN */
+    if (connection && nm_connection_is_type(connection, NM_SETTING_VPN_SETTING_NAME)) {
+        NMSettingVpn *s_vpn = nm_connection_get_setting_vpn(connection);
+
+        if (nm_streq0(nm_setting_vpn_get_service_type(s_vpn),
+                      NM_SECRET_AGENT_VPN_TYPE_OPENCONNECT)) {
+            gs_free char *cookie  = NULL;
+            gs_free char *gateway = NULL;
+            gs_free char *gwcert  = NULL;
+
+            openconnect_authenticate(connection, &cookie, &gateway, &gwcert);
+
+            for (i = 0; i < secrets->len; i++) {
+                NMSecretAgentSimpleSecret *secret = secrets->pdata[i];
+
+                if (secret->secret_type != NM_SECRET_AGENT_SECRET_TYPE_VPN_SECRET)
+                    continue;
+                if (!nm_streq0(secret->vpn_type, NM_SECRET_AGENT_VPN_TYPE_OPENCONNECT))
+                    continue;
+                if (nm_streq0(secret->entry_id,
+                              NM_SECRET_AGENT_ENTRY_ID_PREFX_VPN_SECRETS "cookie")) {
+                    g_free(secret->value);
+                    secret->value = g_steal_pointer(&cookie);
+                } else if (nm_streq0(secret->entry_id,
+                                     NM_SECRET_AGENT_ENTRY_ID_PREFX_VPN_SECRETS "gateway")) {
+                    g_free(secret->value);
+                    secret->value = g_steal_pointer(&gateway);
+                } else if (nm_streq0(secret->entry_id,
+                                     NM_SECRET_AGENT_ENTRY_ID_PREFX_VPN_SECRETS "gwcert")) {
+                    g_free(secret->value);
+                    secret->value = g_steal_pointer(&gwcert);
+                }
+            }
+        }
+    }
+
+    form = nmt_password_dialog_new(request_id, title, msg, secrets);
+    nmt_newt_form_run_sync(form);
+
+    if (nmt_password_dialog_succeeded(NMT_PASSWORD_DIALOG(form)))
+        nm_secret_agent_simple_response(agent, request_id, secrets);
+    else
+        nm_secret_agent_simple_response(agent, request_id, NULL);
+
+    g_object_unref(form);
+}
+
+typedef struct {
+    NMDevice *          device;
+    NMActiveConnection *active;
+    NmtSyncOp *         op;
+} ActivateConnectionInfo;
+
+static void
+connect_cancelled(NmtNewtForm *form, gpointer user_data)
+{
+    ActivateConnectionInfo *info  = user_data;
+    GError *                error = NULL;
+
+    error = g_error_new_literal(G_IO_ERROR, G_IO_ERROR_CANCELLED, "Cancelled");
+    nmt_sync_op_complete_boolean(info->op, FALSE, error);
+    g_clear_error(&error);
+}
+
+static void
+check_activated(ActivateConnectionInfo *info)
+{
+    NMActiveConnectionState ac_state;
+    const char *            reason = NULL;
+    gs_free_error GError *error    = NULL;
+
+    ac_state = nmc_activation_get_effective_state(info->active, info->device, &reason);
+    if (!NM_IN_SET(ac_state,
+                   NM_ACTIVE_CONNECTION_STATE_ACTIVATED,
+                   NM_ACTIVE_CONNECTION_STATE_DEACTIVATED))
+        return;
+
+    if (ac_state == NM_ACTIVE_CONNECTION_STATE_DEACTIVATED) {
+        nm_assert(reason);
+        error = g_error_new(NM_CLIENT_ERROR,
+                            NM_CLIENT_ERROR_FAILED,
+                            _("Activation failed: %s"),
+                            reason);
+    }
+
+    nmt_sync_op_complete_boolean(info->op, error == NULL, error);
+}
+
+static void
+activate_ac_state_changed(GObject *object, GParamSpec *pspec, gpointer user_data)
+{
+    check_activated(user_data);
+}
+
+static void
+activate_device_state_changed(GObject *object, GParamSpec *pspec, gpointer user_data)
+{
+    check_activated(user_data);
+}
+
+static void
+activate_callback(GObject *client, GAsyncResult *result, gpointer user_data)
+{
+    NmtSyncOp *         op = user_data;
+    NMActiveConnection *ac;
+    GError *            error = NULL;
+
+    ac = nm_client_activate_connection_finish(NM_CLIENT(client), result, &error);
+    if (error)
+        nmt_sync_op_complete_pointer(op, NULL, error);
+    else
+        nmt_sync_op_complete_pointer(op, ac, NULL);
+}
+
+static void
+add_and_activate_callback(GObject *client, GAsyncResult *result, gpointer user_data)
+{
+    NmtSyncOp *         op = user_data;
+    NMActiveConnection *ac;
+    GError *            error = NULL;
+
+    ac = nm_client_add_and_activate_connection_finish(NM_CLIENT(client), result, &error);
+    if (error)
+        nmt_sync_op_complete_pointer(op, NULL, error);
+    else
+        nmt_sync_op_complete_pointer(op, ac, NULL);
+}
+
+static void
+deactivate_connection(NMActiveConnection *ac)
+{
+    GError *error = NULL;
+
+    if (!nm_client_deactivate_connection(nm_client, ac, NULL, &error)) {
+        nmt_newt_message_dialog(_("Could not deactivate connection: %s"), error->message);
+        g_clear_error(&error);
+    }
+}
+
+static void
+activate_connection(NMConnection *connection, NMDevice *device, NMObject *specific_object)
+{
+    NmtNewtForm *   form;
+    gs_unref_object NMSecretAgentSimple *agent = NULL;
+    NmtNewtWidget *                      label;
+    NmtSyncOp                            op;
+    const char *                         specific_object_path;
+    NMActiveConnection *                 ac;
+    GError *                             error = NULL;
+    ActivateConnectionInfo               info  = {};
+
+    form  = g_object_new(NMT_TYPE_NEWT_FORM, "escape-exits", TRUE, NULL);
+    label = nmt_newt_label_new(_("Connecting..."));
+    nmt_newt_form_set_content(form, label);
+
+    agent = nm_secret_agent_simple_new("nmtui");
+    if (agent) {
+        if (connection) {
+            nm_secret_agent_simple_enable(agent, nm_object_get_path(NM_OBJECT(connection)));
+        }
+        g_signal_connect(agent,
+                         NM_SECRET_AGENT_SIMPLE_REQUEST_SECRETS,
+                         G_CALLBACK(secrets_requested),
+                         connection);
+    }
+
+    specific_object_path = specific_object ? nm_object_get_path(specific_object) : NULL;
+
+    /* There's no way to cancel an nm_client_activate_connection() /
+     * nm_client_add_and_activate_connection() call, so we always let them
+     * complete, even if the user hits Esc; they shouldn't normally take long
+     * to complete anyway.
+     */
+
+    nmt_sync_op_init(&op);
+    if (connection) {
+        nm_client_activate_connection_async(nm_client,
+                                            connection,
+                                            device,
+                                            specific_object_path,
+                                            NULL,
+                                            activate_callback,
+                                            &op);
+    } else {
+        nm_client_add_and_activate_connection_async(nm_client,
+                                                    NULL,
+                                                    device,
+                                                    specific_object_path,
+                                                    NULL,
+                                                    add_and_activate_callback,
+                                                    &op);
+    }
+
+    nmt_newt_form_show(form);
+
+    ac = nmt_sync_op_wait_pointer(&op, &error);
+    if (!ac) {
+        nmt_newt_message_dialog(_("Could not activate connection: %s"), error->message);
+        g_clear_error(&error);
+        goto done;
+    } else if (nm_active_connection_get_state(ac) == NM_ACTIVE_CONNECTION_STATE_ACTIVATED) {
+        /* Already active */
+        goto done;
+    } else if (!nmt_newt_widget_get_realized(NMT_NEWT_WIDGET(form))) {
+        /* User already hit Esc */
+        goto done;
+    }
+
+    if (agent && !connection) {
+        connection = NM_CONNECTION(nm_active_connection_get_connection(ac));
+        if (connection) {
+            nm_secret_agent_simple_enable(agent, nm_object_get_path(NM_OBJECT(connection)));
+        }
+    }
+
+    /* Now wait for the connection to actually reach the ACTIVATED state,
+     * allowing the user to cancel if it takes too long.
+     */
+    nmt_sync_op_init(&op);
+    info.active = ac;
+    info.device = device;
+    info.op     = &op;
+
+    g_signal_connect(form, "quit", G_CALLBACK(connect_cancelled), &info);
+    g_signal_connect(ac,
+                     "notify::" NM_ACTIVE_CONNECTION_STATE,
+                     G_CALLBACK(activate_ac_state_changed),
+                     &info);
+    if (device) {
+        g_signal_connect(device,
+                         "notify::" NM_DEVICE_STATE,
+                         G_CALLBACK(activate_device_state_changed),
+                         &info);
+    }
+
+    if (!nmt_sync_op_wait_boolean(&op, &error)) {
+        if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+            nmt_newt_message_dialog(_("Could not activate connection: %s"), error->message);
+        g_clear_error(&error);
+    }
+
+    g_signal_handlers_disconnect_by_func(form, G_CALLBACK(connect_cancelled), &info);
+    g_signal_handlers_disconnect_by_func(ac, G_CALLBACK(activate_ac_state_changed), &info);
+    if (device)
+        g_signal_handlers_disconnect_by_func(device,
+                                             G_CALLBACK(activate_device_state_changed),
+                                             &info);
+
+done:
+    if (nmt_newt_widget_get_realized(NMT_NEWT_WIDGET(form)))
+        nmt_newt_form_quit(form);
+    g_object_unref(form);
+
+    if (agent)
+        nm_secret_agent_old_unregister(NM_SECRET_AGENT_OLD(agent), NULL, NULL);
+}
+
+static void
+listbox_activated(NmtNewtListbox *listbox, gpointer user_data)
+{
+    NmtConnectConnectionList *list = NMT_CONNECT_CONNECTION_LIST(listbox);
+    NMConnection *            connection;
+    NMDevice *                device;
+    NMObject *                specific_object;
+    NMActiveConnection *      ac;
+
+    if (!nmt_connect_connection_list_get_selection(list,
+                                                   &connection,
+                                                   &device,
+                                                   &specific_object,
+                                                   &ac))
+        return;
+
+    if (ac)
+        deactivate_connection(ac);
+    else
+        activate_connection(connection, device, specific_object);
+}
+
+static void
+activate_clicked(NmtNewtButton *button, gpointer listbox)
+{
+    listbox_activated(listbox, NULL);
+}
+
+static void
+listbox_active_changed(GObject *object, GParamSpec *pspec, gpointer button)
+{
+    NmtConnectConnectionList *list = NMT_CONNECT_CONNECTION_LIST(object);
+    static const char *       activate, *deactivate;
+    static int                deactivate_padding, activate_padding;
+    NMActiveConnection *      ac;
+    gboolean                  has_selection;
+
+    if (G_UNLIKELY(activate == NULL)) {
+        int activate_width, deactivate_width;
+
+        activate         = _("Activate");
+        activate_width   = nmt_newt_text_width(activate);
+        deactivate       = _("Deactivate");
+        deactivate_width = nmt_newt_text_width(deactivate);
+
+        activate_padding   = MAX(0, deactivate_width - activate_width);
+        deactivate_padding = MAX(0, activate_width - deactivate_width);
+    }
+
+    has_selection = nmt_connect_connection_list_get_selection(list, NULL, NULL, NULL, &ac);
+
+    nmt_newt_component_set_sensitive(button, has_selection);
+    if (has_selection && ac) {
+        nmt_newt_button_set_label(button, deactivate);
+        nmt_newt_widget_set_padding(button, 0, 0, deactivate_padding, 0);
+    } else {
+        nmt_newt_button_set_label(button, activate);
+        nmt_newt_widget_set_padding(button, 0, 0, activate_padding, 0);
+    }
+}
+
+static NmtNewtForm *
+nmt_connect_connection_list(gboolean is_top)
+{
+    int            screen_width, screen_height;
+    NmtNewtForm *  form;
+    NmtNewtWidget *list, *activate, *quit, *bbox, *grid;
+
+    newtGetScreenSize(&screen_width, &screen_height);
+
+    form = g_object_new(NMT_TYPE_NEWT_FORM,
+                        "y",
+                        2,
+                        "height",
+                        screen_height - 4,
+                        "escape-exits",
+                        TRUE,
+                        NULL);
+
+    grid = nmt_newt_grid_new();
+
+    list = nmt_connect_connection_list_new();
+    nmt_newt_grid_add(NMT_NEWT_GRID(grid), list, 0, 0);
+    nmt_newt_grid_set_flags(NMT_NEWT_GRID(grid),
+                            list,
+                            NMT_NEWT_GRID_FILL_X | NMT_NEWT_GRID_FILL_Y | NMT_NEWT_GRID_EXPAND_X
+                                | NMT_NEWT_GRID_EXPAND_Y);
+    g_signal_connect(list, "activated", G_CALLBACK(listbox_activated), NULL);
+
+    bbox = nmt_newt_button_box_new(NMT_NEWT_BUTTON_BOX_VERTICAL);
+    nmt_newt_grid_add(NMT_NEWT_GRID(grid), bbox, 1, 0);
+    nmt_newt_widget_set_padding(bbox, 1, 1, 0, 1);
+
+    activate = nmt_newt_button_box_add_start(NMT_NEWT_BUTTON_BOX(bbox), _("Activate"));
+    g_signal_connect(list, "notify::active", G_CALLBACK(listbox_active_changed), activate);
+    listbox_active_changed(G_OBJECT(list), NULL, activate);
+    g_signal_connect(activate, "clicked", G_CALLBACK(activate_clicked), list);
+
+    quit = nmt_newt_button_box_add_end(NMT_NEWT_BUTTON_BOX(bbox), is_top ? _("Quit") : _("Back"));
+    nmt_newt_widget_set_exit_on_activate(quit, TRUE);
+
+    nmt_newt_form_set_content(form, grid);
+    return form;
+}
+
+static NmtNewtForm *
+nmt_connect_connection(const char *identifier)
+{
+    NmtNewtWidget *     list;
+    NMConnection *      connection;
+    NMDevice *          device;
+    NMObject *          specific_object;
+    NMActiveConnection *ac;
+
+    list = nmt_connect_connection_list_new();
+    if (!nmt_connect_connection_list_get_connection(NMT_CONNECT_CONNECTION_LIST(list),
+                                                    identifier,
+                                                    &connection,
+                                                    &device,
+                                                    &specific_object,
+                                                    &ac))
+        nmt_newt_message_dialog(_("No such connection '%s'"), identifier);
+    else if (ac)
+        nmt_newt_message_dialog(_("Connection is already active"));
+    else
+        activate_connection(connection, device, specific_object);
+    g_object_unref(list);
+
+    return NULL;
+}
+
+NmtNewtForm *
+nmtui_connect(gboolean is_top, int argc, char **argv)
+{
+    if (argc == 2)
+        return nmt_connect_connection(argv[1]);
+    else
+        return nmt_connect_connection_list(is_top);
+}
diff --git a/src/nmtui/nmtui-connect.h b/src/nmtui/nmtui-connect.h
new file mode 100644
index 00000000..811893e8
--- /dev/null
+++ b/src/nmtui/nmtui-connect.h
@@ -0,0 +1,11 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+#ifndef NMTUI_CONNECT_H
+#define NMTUI_CONNECT_H
+
+NmtNewtForm *nmtui_connect(gboolean is_top, int argc, char **argv);
+
+#endif /* NMTUI_CONNECT_H */
diff --git a/src/nmtui/nmtui-edit.c b/src/nmtui/nmtui-edit.c
new file mode 100644
index 00000000..534643a7
--- /dev/null
+++ b/src/nmtui/nmtui-edit.c
@@ -0,0 +1,568 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+/**
+ * SECTION:nmtui-edit
+ * @short_description: nm-connection-editor-like functionality
+ *
+ * nmtui-edit implements editing #NMConnections.
+ */
+
+#include "libnm-client-aux-extern/nm-default-client.h"
+
+#include <stdlib.h>
+
+#include "nmtui.h"
+#include "nmtui-edit.h"
+#include "nmt-edit-connection-list.h"
+#include "nmt-editor.h"
+#include "nmt-utils.h"
+
+#include "nm-editor-utils.h"
+
+static void
+list_add_connection(NmtEditConnectionList *list, gpointer form)
+{
+    nmt_add_connection();
+    nmt_newt_form_set_focus(form, NMT_NEWT_WIDGET(list));
+}
+
+static void
+list_edit_connection(NmtEditConnectionList *list, NMConnection *connection, gpointer form)
+{
+    nmt_edit_connection(connection);
+    nmt_newt_form_set_focus(form, NMT_NEWT_WIDGET(list));
+}
+
+static void
+list_remove_connection(NmtEditConnectionList *list, NMRemoteConnection *connection, gpointer form)
+{
+    nmt_remove_connection(connection);
+    nmt_newt_form_set_focus(form, NMT_NEWT_WIDGET(list));
+}
+
+static gboolean
+edit_connection_list_filter(NmtEditConnectionList *list,
+                            NMConnection *         connection,
+                            gpointer               user_data)
+{
+    NMSettingConnection *s_con;
+    const char *         master, *slave_type;
+    const char *         uuid, *ifname;
+    const GPtrArray *    conns;
+    int                  i;
+    gboolean             found_master = FALSE;
+
+    s_con = nm_connection_get_setting_connection(connection);
+    g_return_val_if_fail(s_con != NULL, FALSE);
+
+    master = nm_setting_connection_get_master(s_con);
+    if (!master)
+        return TRUE;
+    slave_type = nm_setting_connection_get_slave_type(s_con);
+    if (g_strcmp0(slave_type, NM_SETTING_BOND_SETTING_NAME) != 0
+        && g_strcmp0(slave_type, NM_SETTING_TEAM_SETTING_NAME) != 0
+        && g_strcmp0(slave_type, NM_SETTING_BRIDGE_SETTING_NAME) != 0)
+        return TRUE;
+
+    conns = nm_client_get_connections(nm_client);
+    for (i = 0; i < conns->len; i++) {
+        NMConnection *candidate = conns->pdata[i];
+
+        uuid   = nm_connection_get_uuid(candidate);
+        ifname = nm_connection_get_interface_name(candidate);
+        if (!g_strcmp0(master, uuid) || !g_strcmp0(master, ifname)) {
+            found_master = TRUE;
+            break;
+        }
+    }
+
+    return !found_master;
+}
+
+static NmtNewtForm *
+nmt_edit_main_connection_list(gboolean is_top)
+{
+    int            screen_width, screen_height;
+    NmtNewtForm *  form;
+    NmtNewtWidget *quit, *list;
+
+    newtGetScreenSize(&screen_width, &screen_height);
+
+    form = g_object_new(NMT_TYPE_NEWT_FORM,
+                        "y",
+                        2,
+                        "height",
+                        screen_height - 4,
+                        "escape-exits",
+                        TRUE,
+                        NULL);
+
+    quit = nmt_newt_button_new(is_top ? _("Quit") : _("Back"));
+    nmt_newt_widget_set_exit_on_activate(quit, TRUE);
+
+    list = g_object_new(NMT_TYPE_EDIT_CONNECTION_LIST,
+                        "extra-widget",
+                        quit,
+                        "connection-filter",
+                        edit_connection_list_filter,
+                        NULL);
+
+    g_signal_connect(list, "add-connection", G_CALLBACK(list_add_connection), form);
+    g_signal_connect(list, "edit-connection", G_CALLBACK(list_edit_connection), form);
+    g_signal_connect(list, "remove-connection", G_CALLBACK(list_remove_connection), form);
+
+    nmt_newt_form_set_content(form, list);
+    return form;
+}
+
+#define NMT_TYPE_ADD_CONNECTION (nmt_add_connection_get_type())
+#define NMT_ADD_CONNECTION(obj) \
+    (G_TYPE_CHECK_INSTANCE_CAST((obj), NMT_TYPE_ADD_CONNECTION, NmtAddConnection))
+#define NMT_IS_ADD_CONNECTION(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMT_TYPE_ADD_CONNECTION))
+
+typedef NmtNewtForm      NmtAddConnection;
+typedef NmtNewtFormClass NmtAddConnectionClass;
+
+GType nmt_add_connection_get_type(void);
+
+G_DEFINE_TYPE(NmtAddConnection, nmt_add_connection, NMT_TYPE_NEWT_FORM)
+
+#define NMT_ADD_CONNECTION_GET_PRIVATE(o) \
+    (G_TYPE_INSTANCE_GET_PRIVATE((o), NMT_TYPE_ADD_CONNECTION, NmtAddConnectionPrivate))
+
+typedef struct {
+    NmtNewtTextbox *textbox;
+    NmtNewtListbox *listbox;
+
+    char *                     primary_text;
+    char *                     secondary_text;
+    NMConnection *             master;
+    NmtAddConnectionTypeFilter type_filter;
+    gpointer                   type_filter_data;
+
+    gboolean single_type;
+} NmtAddConnectionPrivate;
+
+enum {
+    PROP_0,
+
+    PROP_PRIMARY_TEXT,
+    PROP_SECONDARY_TEXT,
+    PROP_MASTER,
+    PROP_TYPE_FILTER,
+    PROP_TYPE_FILTER_DATA,
+
+    LAST_PROP
+};
+
+static void
+create_connection(NmtNewtWidget *widget, gpointer list)
+{
+    NmtAddConnectionPrivate *priv = NMT_ADD_CONNECTION_GET_PRIVATE(list);
+    GType         type = (GType) GPOINTER_TO_SIZE(nmt_newt_listbox_get_active_key(priv->listbox));
+    NMConnection *connection;
+
+    connection = nm_editor_utils_create_connection(type, priv->master, nm_client);
+    nmt_edit_connection(connection);
+    g_object_unref(connection);
+}
+
+static void
+create_connection_and_quit(NmtNewtWidget *widget, gpointer list)
+{
+    create_connection(widget, list);
+    nmt_newt_form_quit(list);
+}
+
+static void
+nmt_add_connection_init(NmtAddConnection *form)
+{
+    NmtAddConnectionPrivate *priv = NMT_ADD_CONNECTION_GET_PRIVATE(form);
+    NmtNewtWidget *          textbox, *listbox, *button;
+    NmtNewtGrid *            grid, *buttons;
+
+    grid = NMT_NEWT_GRID(nmt_newt_grid_new());
+
+    textbox       = nmt_newt_textbox_new(0, 60);
+    priv->textbox = NMT_NEWT_TEXTBOX(textbox);
+    nmt_newt_grid_add(grid, textbox, 0, 0);
+
+    listbox       = nmt_newt_listbox_new(5, NMT_NEWT_LISTBOX_SCROLL);
+    priv->listbox = NMT_NEWT_LISTBOX(listbox);
+    g_signal_connect(priv->listbox, "activated", G_CALLBACK(create_connection_and_quit), form);
+    nmt_newt_grid_add(grid, listbox, 0, 1);
+    nmt_newt_widget_set_padding(listbox, 0, 1, 0, 0);
+    nmt_newt_grid_set_flags(grid, listbox, NMT_NEWT_GRID_EXPAND_X);
+
+    // FIXME: VPN description textbox
+
+    buttons = NMT_NEWT_GRID(nmt_newt_grid_new());
+    nmt_newt_grid_add(grid, NMT_NEWT_WIDGET(buttons), 0, 2);
+    nmt_newt_widget_set_padding(NMT_NEWT_WIDGET(buttons), 0, 1, 0, 0);
+
+    button = g_object_ref_sink(nmt_newt_button_new(_("Cancel")));
+    nmt_newt_widget_set_exit_on_activate(button, TRUE);
+    nmt_newt_grid_add(NMT_NEWT_GRID(buttons), button, 0, 0);
+    nmt_newt_widget_set_padding(button, 0, 0, 1, 0);
+    nmt_newt_grid_set_flags(NMT_NEWT_GRID(buttons),
+                            button,
+                            NMT_NEWT_GRID_EXPAND_X | NMT_NEWT_GRID_ANCHOR_RIGHT
+                                | NMT_NEWT_GRID_FILL_Y);
+
+    button = g_object_ref_sink(nmt_newt_button_new(_("Create")));
+    g_signal_connect(button, "clicked", G_CALLBACK(create_connection_and_quit), form);
+    nmt_newt_grid_add(NMT_NEWT_GRID(buttons), button, 1, 0);
+
+    nmt_newt_form_set_content(NMT_NEWT_FORM(form), NMT_NEWT_WIDGET(grid));
+}
+
+static void
+nmt_add_connection_constructed(GObject *object)
+{
+    NmtAddConnectionPrivate *    priv = NMT_ADD_CONNECTION_GET_PRIVATE(object);
+    NMEditorConnectionTypeData **types;
+    char *                       text;
+    int                          i, num_types;
+
+    if (priv->secondary_text) {
+        text = g_strdup_printf("%s\n\n%s", priv->primary_text, priv->secondary_text);
+    } else
+        text = g_strdup(priv->primary_text);
+    nmt_newt_textbox_set_text(priv->textbox, text);
+    g_free(text);
+
+    types = nm_editor_utils_get_connection_type_list();
+    for (i = num_types = 0; types[i]; i++) {
+        if (priv->type_filter && !priv->type_filter(types[i]->setting_type, priv->type_filter_data))
+            continue;
+        nmt_newt_listbox_append(priv->listbox,
+                                types[i]->name,
+                                GSIZE_TO_POINTER(types[i]->setting_type));
+        num_types++;
+    }
+
+    if (num_types == 1)
+        priv->single_type = TRUE;
+
+    G_OBJECT_CLASS(nmt_add_connection_parent_class)->constructed(object);
+}
+
+static void
+nmt_add_connection_show(NmtNewtForm *form)
+{
+    NmtAddConnectionPrivate *priv = NMT_ADD_CONNECTION_GET_PRIVATE(form);
+
+    if (priv->single_type) {
+        nmt_newt_listbox_set_active(priv->listbox, 0);
+        create_connection(NMT_NEWT_WIDGET(priv->listbox), g_object_ref(form));
+    } else
+        NMT_NEWT_FORM_CLASS(nmt_add_connection_parent_class)->show(form);
+}
+
+static void
+nmt_add_connection_finalize(GObject *object)
+{
+    NmtAddConnectionPrivate *priv = NMT_ADD_CONNECTION_GET_PRIVATE(object);
+
+    g_free(priv->primary_text);
+    g_free(priv->secondary_text);
+    g_clear_object(&priv->master);
+
+    G_OBJECT_CLASS(nmt_add_connection_parent_class)->finalize(object);
+}
+
+static void
+nmt_add_connection_set_property(GObject *     object,
+                                guint         prop_id,
+                                const GValue *value,
+                                GParamSpec *  pspec)
+{
+    NmtAddConnectionPrivate *priv = NMT_ADD_CONNECTION_GET_PRIVATE(object);
+
+    switch (prop_id) {
+    case PROP_PRIMARY_TEXT:
+        priv->primary_text = g_value_dup_string(value);
+        break;
+    case PROP_SECONDARY_TEXT:
+        priv->secondary_text = g_value_dup_string(value);
+        break;
+    case PROP_MASTER:
+        priv->master = g_value_dup_object(value);
+        break;
+    case PROP_TYPE_FILTER:
+        priv->type_filter = g_value_get_pointer(value);
+        break;
+    case PROP_TYPE_FILTER_DATA:
+        priv->type_filter_data = g_value_get_pointer(value);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nmt_add_connection_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+    NmtAddConnectionPrivate *priv = NMT_ADD_CONNECTION_GET_PRIVATE(object);
+
+    switch (prop_id) {
+    case PROP_PRIMARY_TEXT:
+        g_value_set_string(value, priv->primary_text);
+        break;
+    case PROP_SECONDARY_TEXT:
+        g_value_set_string(value, priv->secondary_text);
+        break;
+    case PROP_MASTER:
+        g_value_set_object(value, priv->master);
+        break;
+    case PROP_TYPE_FILTER:
+        g_value_set_pointer(value, priv->type_filter);
+        break;
+    case PROP_TYPE_FILTER_DATA:
+        g_value_set_pointer(value, priv->type_filter_data);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nmt_add_connection_class_init(NmtAddConnectionClass *add_class)
+{
+    GObjectClass *    object_class = G_OBJECT_CLASS(add_class);
+    NmtNewtFormClass *form_class   = NMT_NEWT_FORM_CLASS(add_class);
+
+    g_type_class_add_private(add_class, sizeof(NmtAddConnectionPrivate));
+
+    /* virtual methods */
+    object_class->constructed  = nmt_add_connection_constructed;
+    object_class->set_property = nmt_add_connection_set_property;
+    object_class->get_property = nmt_add_connection_get_property;
+    object_class->finalize     = nmt_add_connection_finalize;
+
+    form_class->show = nmt_add_connection_show;
+
+    g_object_class_install_property(
+        object_class,
+        PROP_PRIMARY_TEXT,
+        g_param_spec_string("primary-text",
+                            "",
+                            "",
+                            _("Select the type of connection you wish to create."),
+                            G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+    g_object_class_install_property(
+        object_class,
+        PROP_SECONDARY_TEXT,
+        g_param_spec_string("secondary-text",
+                            "",
+                            "",
+#if 0
+                              _("If you are creating a VPN, and the VPN connection you "
+                              "wish to create does not appear in the list, you may "
+                              "not have the correct VPN plugin installed."),
+#else
+                            NULL,
+#endif
+                            G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+    g_object_class_install_property(
+        object_class,
+        PROP_MASTER,
+        g_param_spec_object("master",
+                            "",
+                            "",
+                            NM_TYPE_CONNECTION,
+                            G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+    g_object_class_install_property(
+        object_class,
+        PROP_TYPE_FILTER,
+        g_param_spec_pointer("type-filter",
+                             "",
+                             "",
+                             G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+    g_object_class_install_property(
+        object_class,
+        PROP_TYPE_FILTER_DATA,
+        g_param_spec_pointer("type-filter-data",
+                             "",
+                             "",
+                             G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+}
+
+void
+nmt_add_connection(void)
+{
+    NmtNewtForm *form;
+
+    form = g_object_new(NMT_TYPE_ADD_CONNECTION, "title", _("New Connection"), NULL);
+    nmt_newt_form_show(form);
+    g_object_unref(form);
+}
+
+void
+nmt_add_connection_full(const char *               primary_text,
+                        const char *               secondary_text,
+                        NMConnection *             master,
+                        NmtAddConnectionTypeFilter type_filter,
+                        gpointer                   type_filter_data)
+{
+    NmtNewtForm *form;
+
+    form = g_object_new(NMT_TYPE_ADD_CONNECTION,
+                        "title",
+                        _("New Connection"),
+                        "primary-text",
+                        primary_text,
+                        "secondary-text",
+                        secondary_text,
+                        "master",
+                        master,
+                        "type-filter",
+                        type_filter,
+                        "type-filter-data",
+                        type_filter_data,
+                        NULL);
+    nmt_newt_form_show(form);
+    g_object_unref(form);
+}
+
+void
+nmt_edit_connection(NMConnection *connection)
+{
+    NmtNewtForm *editor;
+
+    editor = nmt_editor_new(connection);
+    if (!editor)
+        return;
+
+    nmt_newt_form_show(editor);
+    g_object_unref(editor);
+}
+
+typedef struct {
+    NmtSyncOp           op;
+    gboolean            got_callback, got_signal;
+    NMRemoteConnection *connection;
+} ConnectionDeleteData;
+
+static void
+connection_deleted_callback(GObject *connection, GAsyncResult *result, gpointer user_data)
+{
+    ConnectionDeleteData *data  = user_data;
+    GError *              error = NULL;
+
+    if (!nm_remote_connection_delete_finish(data->connection, result, &error)) {
+        nmt_newt_message_dialog(_("Unable to delete connection: %s"), error->message);
+    } else
+        data->got_callback = TRUE;
+
+    if (error || (data->got_callback && data->got_signal))
+        nmt_sync_op_complete_boolean(&data->op, error == NULL, error);
+    g_clear_error(&error);
+}
+
+static void
+connection_removed_signal(NMClient *client, NMRemoteConnection *connection, gpointer user_data)
+{
+    ConnectionDeleteData *data = user_data;
+
+    if (connection == data->connection) {
+        data->got_signal = TRUE;
+        if (data->got_callback && data->got_signal)
+            nmt_sync_op_complete_boolean(&data->op, TRUE, NULL);
+    }
+}
+
+static void
+remove_one_connection(NMRemoteConnection *connection)
+{
+    ConnectionDeleteData data;
+    GError *             error = NULL;
+
+    data.got_callback = data.got_signal = FALSE;
+    nmt_sync_op_init(&data.op);
+
+    data.connection = connection;
+    g_signal_connect(nm_client,
+                     NM_CLIENT_CONNECTION_REMOVED,
+                     G_CALLBACK(connection_removed_signal),
+                     &data);
+    nm_remote_connection_delete_async(connection, NULL, connection_deleted_callback, &data);
+
+    if (!nmt_sync_op_wait_boolean(&data.op, &error)) {
+        nmt_newt_message_dialog(_("Could not delete connection '%s': %s"),
+                                nm_connection_get_id(NM_CONNECTION(connection)),
+                                error->message);
+        g_error_free(error);
+    }
+
+    g_signal_handlers_disconnect_by_func(nm_client, G_CALLBACK(connection_removed_signal), &data);
+}
+
+void
+nmt_remove_connection(NMRemoteConnection *connection)
+{
+    const GPtrArray *    all_conns;
+    GSList *             slaves, *iter;
+    int                  i;
+    NMRemoteConnection * slave;
+    NMSettingConnection *s_con;
+    const char *         uuid, *iface, *master;
+    int                  choice;
+
+    choice = nmt_newt_choice_dialog(_("Cancel"),
+                                    _("Delete"),
+                                    _("Are you sure you want to delete the connection '%s'?"),
+                                    nm_connection_get_id(NM_CONNECTION(connection)));
+    if (choice == 1)
+        return;
+
+    g_object_ref(connection);
+    remove_one_connection(connection);
+
+    uuid  = nm_connection_get_uuid(NM_CONNECTION(connection));
+    iface = nm_connection_get_interface_name(NM_CONNECTION(connection));
+
+    all_conns = nm_client_get_connections(nm_client);
+    slaves    = NULL;
+    for (i = 0; i < all_conns->len; i++) {
+        slave  = all_conns->pdata[i];
+        s_con  = nm_connection_get_setting_connection(NM_CONNECTION(slave));
+        master = nm_setting_connection_get_master(s_con);
+        if (master) {
+            if (!g_strcmp0(master, uuid) || !g_strcmp0(master, iface))
+                slaves = g_slist_prepend(slaves, g_object_ref(slave));
+        }
+    }
+
+    for (iter = slaves; iter; iter = iter->next)
+        remove_one_connection(iter->data);
+    g_slist_free_full(slaves, g_object_unref);
+
+    g_object_unref(connection);
+}
+
+NmtNewtForm *
+nmtui_edit(gboolean is_top, int argc, char **argv)
+{
+    NMConnection *conn = NULL;
+
+    if (argc == 2) {
+        if (nm_utils_is_uuid(argv[1]))
+            conn = NM_CONNECTION(nm_client_get_connection_by_uuid(nm_client, argv[1]));
+        if (!conn)
+            conn = NM_CONNECTION(nm_client_get_connection_by_id(nm_client, argv[1]));
+
+        if (!conn) {
+            nmt_newt_message_dialog("%s: no such connection '%s'\n", argv[0], argv[1]);
+            return NULL;
+        }
+
+        return nmt_editor_new(conn);
+    } else
+        return nmt_edit_main_connection_list(is_top);
+}
diff --git a/src/nmtui/nmtui-edit.h b/src/nmtui/nmtui-edit.h
new file mode 100644
index 00000000..99e370b4
--- /dev/null
+++ b/src/nmtui/nmtui-edit.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+#ifndef NMTUI_EDIT_H
+#define NMTUI_EDIT_H
+
+#include "libnmt-newt/nmt-newt.h"
+
+typedef gboolean (*NmtAddConnectionTypeFilter)(GType connection_type, gpointer user_data);
+
+NmtNewtForm *nmtui_edit(gboolean is_top, int argc, char **argv);
+
+void nmt_add_connection(void);
+void nmt_add_connection_full(const char *               primary_text,
+                             const char *               secondary_text,
+                             NMConnection *             master,
+                             NmtAddConnectionTypeFilter type_filter,
+                             gpointer                   type_filter_data);
+
+void nmt_edit_connection(NMConnection *connection);
+
+void nmt_remove_connection(NMRemoteConnection *connection);
+
+#endif /* NMTUI_EDIT_H */
diff --git a/src/nmtui/nmtui-hostname.c b/src/nmtui/nmtui-hostname.c
new file mode 100644
index 00000000..2f252428
--- /dev/null
+++ b/src/nmtui/nmtui-hostname.c
@@ -0,0 +1,106 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+/**
+ * SECTION:nmtui-hostname
+ * @short_description: hostname-setting functionality
+ *
+ * nmtui-hostname implements the "set hostname" functionality
+ */
+
+#include "libnm-client-aux-extern/nm-default-client.h"
+
+#include "libnmt-newt/nmt-newt.h"
+
+#include "nmtui.h"
+#include "nmtui-hostname.h"
+#include "nmt-utils.h"
+
+static char *
+nmtui_hostname_run_dialog(void)
+{
+    NmtNewtForm *     form;
+    NmtNewtWidget *   widget, *ok, *cancel;
+    NmtNewtGrid *     grid;
+    NmtNewtEntry *    entry;
+    NmtNewtButtonBox *bbox;
+    char *            hostname, *ret = NULL;
+
+    form = g_object_new(NMT_TYPE_NEWT_FORM, "title", _("Set Hostname"), "escape-exits", TRUE, NULL);
+
+    widget = nmt_newt_grid_new();
+    nmt_newt_form_set_content(form, widget);
+    grid = NMT_NEWT_GRID(widget);
+
+    widget = nmt_newt_label_new(_("Hostname"));
+    nmt_newt_grid_add(grid, widget, 0, 0);
+
+    widget = nmt_newt_entry_new(40, 0);
+    nmt_newt_widget_set_exit_on_activate(widget, TRUE);
+    nmt_newt_grid_add(grid, widget, 1, 0);
+    nmt_newt_widget_set_padding(widget, 1, 0, 0, 0);
+    entry = NMT_NEWT_ENTRY(widget);
+
+    widget = nmt_newt_button_box_new(NMT_NEWT_BUTTON_BOX_HORIZONTAL);
+    nmt_newt_grid_add(grid, widget, 1, 1);
+    nmt_newt_widget_set_padding(widget, 0, 1, 0, 0);
+    bbox = NMT_NEWT_BUTTON_BOX(widget);
+
+    cancel = nmt_newt_button_box_add_end(bbox, _("Cancel"));
+    nmt_newt_widget_set_exit_on_activate(cancel, TRUE);
+    ok = nmt_newt_button_box_add_end(bbox, _("OK"));
+    nmt_newt_widget_set_exit_on_activate(ok, TRUE);
+
+    g_object_get(G_OBJECT(nm_client), NM_CLIENT_HOSTNAME, &hostname, NULL);
+    nmt_newt_entry_set_text(entry, hostname);
+    g_free(hostname);
+
+    widget = nmt_newt_form_run_sync(form);
+    if (widget == (NmtNewtWidget *) entry || widget == ok)
+        ret = g_strdup(nmt_newt_entry_get_text(entry));
+
+    g_object_unref(form);
+    return ret;
+}
+
+static void
+hostname_set(GObject *object, GAsyncResult *result, gpointer op)
+{
+    GError *error = NULL;
+
+    nm_client_save_hostname_finish(NM_CLIENT(object), result, &error);
+    nmt_sync_op_complete_boolean(op, error == NULL, error);
+    g_clear_error(&error);
+}
+
+NmtNewtForm *
+nmtui_hostname(gboolean is_top, int argc, char **argv)
+{
+    const char *hostname;
+    char *      tmp = NULL;
+    NmtSyncOp   op;
+    GError *    error = NULL;
+
+    if (argc == 2)
+        hostname = argv[1];
+    else
+        hostname = tmp = nmtui_hostname_run_dialog();
+
+    if (hostname) {
+        nmt_sync_op_init(&op);
+        nm_client_save_hostname_async(nm_client, hostname, NULL, hostname_set, &op);
+        if (nmt_sync_op_wait_boolean(&op, &error)) {
+            /* TRANSLATORS: this indicates the result. ie, "I have set the hostname to ..." */
+            nmt_newt_message_dialog(_("Set hostname to '%s'"), hostname);
+        } else {
+            nmt_newt_message_dialog(_("Unable to set hostname: %s"), error->message);
+            g_error_free(error);
+        }
+
+        g_free(tmp);
+    }
+
+    return NULL;
+}
diff --git a/src/nmtui/nmtui-hostname.h b/src/nmtui/nmtui-hostname.h
new file mode 100644
index 00000000..51abef34
--- /dev/null
+++ b/src/nmtui/nmtui-hostname.h
@@ -0,0 +1,11 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+#ifndef NMTUI_HOSTNAME_H
+#define NMTUI_HOSTNAME_H
+
+NmtNewtForm *nmtui_hostname(gboolean is_top, int argc, char **argv);
+
+#endif /* NMTUI_HOSTNAME_H */
diff --git a/src/nmtui/nmtui.c b/src/nmtui/nmtui.c
new file mode 100644
index 00000000..5edf70e5
--- /dev/null
+++ b/src/nmtui/nmtui.c
@@ -0,0 +1,297 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+/**
+ * SECTION:nmtui
+ * @short_description: nmtui toplevel
+ *
+ * The top level of nmtui. Exists mostly just to call nmtui_connect(),
+ * nmtui_edit(), and nmtui_hostname().
+ */
+
+#include "libnm-client-aux-extern/nm-default-client.h"
+
+#include "nmtui.h"
+
+#include <locale.h>
+#include <stdlib.h>
+
+#include "libnm-client-aux-extern/nm-libnm-aux.h"
+
+#include "libnmt-newt/nmt-newt.h"
+#include "nm-editor-bindings.h"
+
+#include "nmtui-edit.h"
+#include "nmtui-connect.h"
+#include "nmtui-hostname.h"
+
+NMClient *        nm_client;
+static GMainLoop *loop;
+
+typedef NmtNewtForm *(*NmtuiSubprogram)(gboolean is_top, int argc, char **argv);
+
+static const struct {
+    const char *    name, *shortcut, *arg;
+    const char *    display_name;
+    NmtuiSubprogram func;
+} subprograms[] = {
+    {"edit", "nmtui-edit", N_("connection"), N_("Edit a connection"), nmtui_edit},
+    {"connect", "nmtui-connect", N_("connection"), N_("Activate a connection"), nmtui_connect},
+    {"hostname", "nmtui-hostname", N_("new hostname"), N_("Set system hostname"), nmtui_hostname}};
+static const int    num_subprograms = G_N_ELEMENTS(subprograms);
+static NmtNewtForm *toplevel_form;
+
+static NmtNewtForm *
+quit_func(int argc, char **argv)
+{
+    if (toplevel_form)
+        nmt_newt_form_quit(toplevel_form);
+
+    nmtui_quit();
+
+    return NULL;
+}
+
+static void
+main_list_activated(NmtNewtWidget *widget, NmtNewtListbox *listbox)
+{
+    NmtNewtForm *   form;
+    NmtuiSubprogram sub;
+
+    sub = nmt_newt_listbox_get_active_key(listbox);
+    if (sub) {
+        form = sub(FALSE, 0, NULL);
+        if (form) {
+            nmt_newt_form_show(form);
+            g_object_unref(form);
+        }
+    }
+}
+
+static NmtNewtForm *
+nmtui_main(gboolean is_top, int argc, char **argv)
+{
+    NmtNewtForm *     form;
+    NmtNewtWidget *   widget, *ok;
+    NmtNewtGrid *     grid;
+    NmtNewtListbox *  listbox;
+    NmtNewtButtonBox *bbox;
+    int               i;
+
+    form = g_object_new(NMT_TYPE_NEWT_FORM,
+                        "title",
+                        _("NetworkManager TUI"),
+                        "escape-exits",
+                        TRUE,
+                        NULL);
+
+    widget = nmt_newt_grid_new();
+    nmt_newt_form_set_content(form, widget);
+    grid = NMT_NEWT_GRID(widget);
+
+    widget = nmt_newt_label_new(_("Please select an option"));
+    nmt_newt_grid_add(grid, widget, 0, 0);
+
+    widget = g_object_new(NMT_TYPE_NEWT_LISTBOX,
+                          "height",
+                          num_subprograms + 2,
+                          "skip-null-keys",
+                          TRUE,
+                          NULL);
+    nmt_newt_grid_add(grid, widget, 0, 1);
+    nmt_newt_widget_set_padding(widget, 0, 1, 0, 1);
+    listbox = NMT_NEWT_LISTBOX(widget);
+    g_signal_connect(widget, "activated", G_CALLBACK(main_list_activated), listbox);
+
+    for (i = 0; i < num_subprograms; i++) {
+        nmt_newt_listbox_append(listbox, _(subprograms[i].display_name), subprograms[i].func);
+    }
+    nmt_newt_listbox_append(listbox, "", NULL);
+    nmt_newt_listbox_append(listbox, _("Quit"), quit_func);
+
+    widget = nmt_newt_button_box_new(NMT_NEWT_BUTTON_BOX_HORIZONTAL);
+    nmt_newt_grid_add(grid, widget, 0, 2);
+    bbox = NMT_NEWT_BUTTON_BOX(widget);
+
+    ok = nmt_newt_button_box_add_end(bbox, _("OK"));
+    g_signal_connect(ok, "activated", G_CALLBACK(main_list_activated), listbox);
+
+    toplevel_form = form;
+
+    return form;
+}
+
+/**
+ * nmtui_quit:
+ *
+ * Causes nmtui to exit.
+ */
+void
+nmtui_quit(void)
+{
+    g_main_loop_quit(loop);
+}
+
+static void
+usage(void)
+{
+    const char *argv0     = g_get_prgname();
+    const char *usage_str = _("Usage");
+    int         i;
+
+    for (i = 0; i < num_subprograms; i++) {
+        if (!strcmp(argv0, subprograms[i].shortcut)) {
+            g_printerr("%s: %s [%s]\n", usage_str, argv0, _(subprograms[i].arg));
+            exit(1);
+        }
+    }
+
+    g_printerr("%s: nmtui\n", usage_str);
+    for (i = 0; i < num_subprograms; i++) {
+        g_printerr("%*s  nmtui %s [%s]\n",
+                   nmt_newt_text_width(usage_str),
+                   " ",
+                   subprograms[i].name,
+                   _(subprograms[i].arg));
+    }
+    exit(1);
+}
+
+typedef struct {
+    NmtuiSubprogram subprogram;
+    int             argc;
+    char **         argv;
+} NmtuiStartupData;
+
+static void
+toplevel_form_quit(NmtNewtForm *form, gpointer user_data)
+{
+    nmtui_quit();
+}
+
+static gboolean
+idle_run_subprogram(gpointer user_data)
+{
+    NmtuiStartupData *data = user_data;
+    NmtNewtForm *     form;
+
+    form = data->subprogram(TRUE, data->argc, data->argv);
+    if (form) {
+        g_signal_connect(form, "quit", G_CALLBACK(toplevel_form_quit), NULL);
+        nmt_newt_form_show(form);
+        g_object_unref(form);
+    } else
+        nmtui_quit();
+
+    return FALSE;
+}
+
+gboolean sleep_on_startup = FALSE;
+gboolean noinit           = FALSE;
+
+GOptionEntry entries[] = {{"sleep",
+                           's',
+                           G_OPTION_FLAG_HIDDEN,
+                           G_OPTION_ARG_NONE,
+                           &sleep_on_startup,
+                           "Sleep on startup",
+                           NULL},
+                          {"noinit",
+                           'n',
+                           G_OPTION_FLAG_HIDDEN,
+                           G_OPTION_ARG_NONE,
+                           &noinit,
+                           "Don't initialize newt",
+                           NULL},
+                          {NULL}};
+
+int
+main(int argc, char **argv)
+{
+    GOptionContext * opts;
+    GError *         error = NULL;
+    NmtuiStartupData startup_data;
+    const char *     prgname;
+    int              i;
+
+    setlocale(LC_ALL, "");
+    bindtextdomain(GETTEXT_PACKAGE, NMLOCALEDIR);
+    bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
+    textdomain(GETTEXT_PACKAGE);
+
+    opts = g_option_context_new(NULL);
+    g_option_context_add_main_entries(opts, entries, NULL);
+
+    if (!g_option_context_parse(opts, &argc, &argv, &error)) {
+        g_printerr("%s: %s: %s\n", argv[0], _("Could not parse arguments"), error->message);
+        exit(1);
+    }
+    g_option_context_free(opts);
+
+    nm_editor_bindings_init();
+
+    if (!nmc_client_new_waitsync(NULL,
+                                 &nm_client,
+                                 &error,
+                                 NM_CLIENT_INSTANCE_FLAGS,
+                                 (guint) NM_CLIENT_INSTANCE_FLAGS_NO_AUTO_FETCH_PERMISSIONS,
+                                 NULL)) {
+        g_printerr(_("Could not contact NetworkManager: %s.\n"), error->message);
+        g_error_free(error);
+        exit(1);
+    }
+    if (!nm_client_get_nm_running(nm_client)) {
+        g_printerr("%s\n", _("NetworkManager is not running."));
+        exit(1);
+    }
+
+    if (sleep_on_startup)
+        sleep(5);
+
+    startup_data.subprogram = NULL;
+    prgname                 = g_get_prgname();
+    if (g_str_has_prefix(prgname, "lt-"))
+        prgname += 3;
+    if (!strcmp(prgname, "nmtui")) {
+        if (argc > 1) {
+            for (i = 0; i < num_subprograms; i++) {
+                if (!strcmp(argv[1], subprograms[i].name)) {
+                    argc--;
+                    argv[0] = (char *) subprograms[i].shortcut;
+                    memmove(&argv[1], &argv[2], argc * sizeof(char *));
+                    startup_data.subprogram = subprograms[i].func;
+                    break;
+                }
+            }
+        } else
+            startup_data.subprogram = nmtui_main;
+    } else {
+        for (i = 0; i < num_subprograms; i++) {
+            if (!strcmp(prgname, subprograms[i].shortcut)) {
+                startup_data.subprogram = subprograms[i].func;
+                break;
+            }
+        }
+    }
+    if (!startup_data.subprogram)
+        usage();
+
+    if (!noinit)
+        nmt_newt_init();
+
+    startup_data.argc = argc;
+    startup_data.argv = argv;
+    g_idle_add(idle_run_subprogram, &startup_data);
+    loop = g_main_loop_new(NULL, FALSE);
+    g_main_loop_run(loop);
+    g_main_loop_unref(loop);
+
+    if (!noinit)
+        nmt_newt_finished();
+
+    g_object_unref(nm_client);
+
+    return 0;
+}
diff --git a/src/nmtui/nmtui.h b/src/nmtui/nmtui.h
new file mode 100644
index 00000000..db67ff57
--- /dev/null
+++ b/src/nmtui/nmtui.h
@@ -0,0 +1,13 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+#ifndef NMTUI_H
+#define NMTUI_H
+
+extern NMClient *nm_client;
+
+void nmtui_quit(void);
+
+#endif /* NMTUI_H */