feat: add TUI for configuring network interfaces settings
Allows configuring: - cidr. - dhcp enable/disable. - MTU. - Ignore. - Dhcp metric. Signed-off-by: Artem Chernyshev <artem.0xD2@gmail.com>
This commit is contained in:
parent
7811589db8
commit
63e0d02aa9
@ -721,8 +721,29 @@ message EtcdMemberListResponse { repeated EtcdMemberList messages = 1; }
|
||||
|
||||
// rpc generateConfiguration
|
||||
|
||||
message RouteConfig {
|
||||
string network = 1;
|
||||
string gateway = 2;
|
||||
uint32 metric = 3;
|
||||
}
|
||||
|
||||
message DHCPOptionsConfig {
|
||||
uint32 route_metric = 1;
|
||||
}
|
||||
|
||||
message NetworkDeviceConfig {
|
||||
string interface = 1;
|
||||
string cidr = 2;
|
||||
int32 mtu = 3;
|
||||
bool dhcp = 4;
|
||||
bool ignore = 5;
|
||||
DHCPOptionsConfig dhcp_options = 6;
|
||||
repeated RouteConfig routes = 7;
|
||||
}
|
||||
|
||||
message NetworkConfig {
|
||||
string hostname = 1;
|
||||
repeated NetworkDeviceConfig interfaces = 2;
|
||||
}
|
||||
|
||||
message InstallConfig {
|
||||
|
@ -35,12 +35,38 @@ func Generate(ctx context.Context, in *machine.GenerateConfigurationRequest) (re
|
||||
|
||||
options := []generate.GenOption{}
|
||||
|
||||
if in.MachineConfig.NetworkConfig != nil && in.MachineConfig.NetworkConfig.Hostname != "" {
|
||||
options = append(options, generate.WithNetworkConfig(
|
||||
&v1alpha1.NetworkConfig{
|
||||
NetworkHostname: in.MachineConfig.NetworkConfig.Hostname,
|
||||
},
|
||||
))
|
||||
if in.MachineConfig.NetworkConfig != nil {
|
||||
networkConfig := &v1alpha1.NetworkConfig{
|
||||
NetworkHostname: in.MachineConfig.NetworkConfig.Hostname,
|
||||
}
|
||||
|
||||
networkInterfaces := in.MachineConfig.NetworkConfig.Interfaces
|
||||
if len(networkInterfaces) > 0 {
|
||||
networkConfig.NetworkInterfaces = make([]*v1alpha1.Device, len(networkInterfaces))
|
||||
|
||||
for i, device := range networkInterfaces {
|
||||
routes := make([]*v1alpha1.Route, len(device.Routes))
|
||||
|
||||
iface := &v1alpha1.Device{
|
||||
DeviceInterface: device.Interface,
|
||||
DeviceMTU: int(device.Mtu),
|
||||
DeviceCIDR: device.Cidr,
|
||||
DeviceDHCP: device.Dhcp,
|
||||
DeviceIgnore: device.Ignore,
|
||||
DeviceRoutes: routes,
|
||||
}
|
||||
|
||||
if device.DhcpOptions != nil {
|
||||
iface.DeviceDHCPOptions = &v1alpha1.DHCPOptions{
|
||||
DHCPRouteMetric: device.DhcpOptions.RouteMetric,
|
||||
}
|
||||
}
|
||||
|
||||
networkConfig.NetworkInterfaces[i] = iface
|
||||
}
|
||||
}
|
||||
|
||||
options = append(options, generate.WithNetworkConfig(networkConfig))
|
||||
}
|
||||
|
||||
if in.MachineConfig.InstallConfig != nil {
|
||||
|
@ -5,30 +5,251 @@
|
||||
package components
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/rivo/tview"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
var hline string
|
||||
|
||||
func init() {
|
||||
for i := 0; i < 2000; i++ {
|
||||
hline += string(tcell.RuneHLine)
|
||||
}
|
||||
}
|
||||
|
||||
// Separator hline with a description below.
|
||||
type Separator struct {
|
||||
*tview.TextView
|
||||
}
|
||||
|
||||
// GetHeight implements Multiline interface.
|
||||
func (s *Separator) GetHeight() int {
|
||||
return 2
|
||||
}
|
||||
|
||||
// NewSeparator creates new form special form item which can be used as a sections Separator.
|
||||
func NewSeparator(description string) *Item {
|
||||
return NewItem("", "", func(item *Item) tview.Primitive {
|
||||
s := &Separator{
|
||||
tview.NewTextView(),
|
||||
}
|
||||
s.SetText(hline + "\n" + description)
|
||||
s.SetWrap(false)
|
||||
|
||||
return s
|
||||
})
|
||||
}
|
||||
|
||||
// Item represents a single form item.
|
||||
type Item struct {
|
||||
Name string
|
||||
description string
|
||||
dest interface{}
|
||||
options []interface{}
|
||||
}
|
||||
|
||||
// TableHeaders represents table headers list for item options which are using table representation.
|
||||
type TableHeaders []interface{}
|
||||
|
||||
// NewTableHeaders creates TableHeaders object.
|
||||
func NewTableHeaders(headers ...interface{}) TableHeaders {
|
||||
return TableHeaders(headers)
|
||||
}
|
||||
|
||||
// NewItem creates new form item.
|
||||
func NewItem(name, description string, dest interface{}, options ...interface{}) *Item {
|
||||
return &Item{
|
||||
Name: name,
|
||||
dest: dest,
|
||||
description: description,
|
||||
options: options,
|
||||
}
|
||||
}
|
||||
|
||||
func (item *Item) assign(value string) error {
|
||||
// rely on yaml parser to decode value into the right type
|
||||
return yaml.Unmarshal([]byte(value), item.dest)
|
||||
}
|
||||
|
||||
// createFormItems dynamically creates tview.FormItem list based on the wrapped type.
|
||||
// nolint:gocyclo
|
||||
func (item *Item) createFormItems() ([]tview.Primitive, error) {
|
||||
res := []tview.Primitive{}
|
||||
|
||||
v := reflect.ValueOf(item.dest)
|
||||
if v.Kind() == reflect.Ptr {
|
||||
v = v.Elem()
|
||||
}
|
||||
|
||||
var formItem tview.Primitive
|
||||
|
||||
label := fmt.Sprintf("[::b]%s[::-]:", item.Name)
|
||||
addDescription := true
|
||||
|
||||
// nolint:exhaustive
|
||||
switch v.Kind() {
|
||||
case reflect.Func:
|
||||
if f, ok := item.dest.(func(*Item) tview.Primitive); ok {
|
||||
formItem = f(item)
|
||||
}
|
||||
case reflect.Bool:
|
||||
// use checkbox for boolean fields
|
||||
checkbox := tview.NewCheckbox()
|
||||
checkbox.SetChangedFunc(func(checked bool) {
|
||||
v.Set(reflect.ValueOf(checked))
|
||||
})
|
||||
checkbox.SetChecked(v.Bool())
|
||||
checkbox.SetLabel(label)
|
||||
formItem = checkbox
|
||||
default:
|
||||
if len(item.options) > 0 {
|
||||
tableHeaders, ok := item.options[0].(TableHeaders)
|
||||
if ok {
|
||||
table := NewTable()
|
||||
table.SetHeader(tableHeaders...)
|
||||
|
||||
addDescription = false
|
||||
|
||||
data := item.options[1:]
|
||||
numColumns := len(tableHeaders)
|
||||
|
||||
if len(data)%numColumns != 0 {
|
||||
return nil, fmt.Errorf("incorrect amount of data provided for the table")
|
||||
}
|
||||
|
||||
selected := -1
|
||||
|
||||
for i := 0; i < len(data); i += numColumns {
|
||||
table.AddRow(data[i : i+numColumns]...)
|
||||
|
||||
if v.Interface() == data[i] {
|
||||
selected = i / numColumns
|
||||
}
|
||||
}
|
||||
|
||||
if selected != -1 {
|
||||
table.SelectRow(selected)
|
||||
}
|
||||
|
||||
formItem = table
|
||||
table.SetRowSelectedFunc(func(row int) {
|
||||
v.Set(reflect.ValueOf(table.GetValue(row, 0))) // always pick the first column
|
||||
})
|
||||
} else {
|
||||
dropdown := tview.NewDropDown()
|
||||
|
||||
if len(item.options)%2 != 0 {
|
||||
return nil, fmt.Errorf("wrong amount of arguments for options: should be even amount of key, value pairs")
|
||||
}
|
||||
|
||||
for i := 0; i < len(item.options); i += 2 {
|
||||
if optionName, ok := item.options[i].(string); ok {
|
||||
selected := -1
|
||||
|
||||
func(index int) {
|
||||
dropdown.AddOption(optionName, func() {
|
||||
v.Set(reflect.ValueOf(item.options[index]))
|
||||
})
|
||||
|
||||
if v.Interface() == item.options[index] {
|
||||
selected = i / 2
|
||||
}
|
||||
}(i + 1)
|
||||
|
||||
if selected != -1 {
|
||||
dropdown.SetCurrentOption(selected)
|
||||
}
|
||||
} else {
|
||||
return nil, fmt.Errorf("expected string option name, got %s", item.options[i])
|
||||
}
|
||||
}
|
||||
|
||||
dropdown.SetLabel(label)
|
||||
|
||||
formItem = dropdown
|
||||
}
|
||||
} else {
|
||||
input := tview.NewInputField()
|
||||
formItem = input
|
||||
input.SetLabel(label)
|
||||
text, err := yaml.Marshal(item.dest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
input.SetText(string(text))
|
||||
input.SetChangedFunc(func(text string) {
|
||||
if err := item.assign(text); err != nil {
|
||||
// TODO: highlight red
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
res = append(res, formItem)
|
||||
|
||||
if item.description != "" && addDescription {
|
||||
parts := strings.Split(item.description, "\n")
|
||||
for _, part := range parts {
|
||||
desc := NewFormLabel(part)
|
||||
res = append(res, desc)
|
||||
}
|
||||
}
|
||||
|
||||
res = append(res, NewFormLabel(""))
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// NewForm creates a new form.
|
||||
func NewForm() *Form {
|
||||
return &Form{
|
||||
func NewForm(app *tview.Application) *Form {
|
||||
f := &Form{
|
||||
Flex: tview.NewFlex().SetDirection(tview.FlexRow),
|
||||
formItems: []tview.FormItem{},
|
||||
form: tview.NewFlex().SetDirection(tview.FlexRow),
|
||||
buttons: tview.NewFlex(),
|
||||
group: NewGroup(app),
|
||||
}
|
||||
|
||||
f.Flex.AddItem(f.form, 0, 1, false)
|
||||
f.Flex.AddItem(f.buttons, 0, 0, false)
|
||||
|
||||
f.SetInputCapture(func(e *tcell.EventKey) *tcell.EventKey {
|
||||
// nolint:exhaustive
|
||||
switch e.Key() {
|
||||
case tcell.KeyTAB:
|
||||
f.group.NextFocus()
|
||||
case tcell.KeyBacktab:
|
||||
f.group.PrevFocus()
|
||||
}
|
||||
|
||||
return e
|
||||
})
|
||||
|
||||
return f
|
||||
}
|
||||
|
||||
// Form is a more flexible form component for tview lib.
|
||||
type Form struct {
|
||||
*tview.Flex
|
||||
formItems []tview.FormItem
|
||||
maxLabelLen int
|
||||
form *tview.Flex
|
||||
buttons *tview.Flex
|
||||
formItems []tview.FormItem
|
||||
maxLabelLen int
|
||||
hasMenuButton bool
|
||||
group *Group
|
||||
}
|
||||
|
||||
// AddFormItem adds a new item to the form.
|
||||
func (f *Form) AddFormItem(item tview.Primitive) {
|
||||
if formItem, ok := item.(tview.FormItem); ok {
|
||||
f.formItems = append(f.formItems, formItem)
|
||||
labelLen := len(formItem.GetLabel()) + 1
|
||||
labelLen := tview.TaggedStringWidth(formItem.GetLabel()) + 1
|
||||
|
||||
if labelLen > f.maxLabelLen {
|
||||
for _, item := range f.formItems[:len(f.formItems)-1] {
|
||||
@ -62,7 +283,57 @@ func (f *Form) AddFormItem(item tview.Primitive) {
|
||||
height = multiline.GetHeight()
|
||||
}
|
||||
|
||||
f.AddItem(item, height+1, 1, false)
|
||||
f.form.AddItem(item, height, 1, false)
|
||||
|
||||
switch item.(type) {
|
||||
case *FormLabel:
|
||||
case *Separator:
|
||||
default:
|
||||
f.group.AddElement(item)
|
||||
}
|
||||
}
|
||||
|
||||
// Focus overrides default focus behavior.
|
||||
func (f *Form) Focus(delegate func(tview.Primitive)) {
|
||||
f.group.FocusFirst()
|
||||
}
|
||||
|
||||
// AddMenuButton adds a button to the menu at the bottom of the form.
|
||||
func (f *Form) AddMenuButton(label string, alignRight bool) *tview.Button {
|
||||
b := tview.NewButton(label)
|
||||
|
||||
if f.hasMenuButton || alignRight {
|
||||
f.buttons.AddItem(
|
||||
tview.NewBox().SetBackgroundColor(f.GetBackgroundColor()),
|
||||
0,
|
||||
1,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
f.ResizeItem(f.buttons, 3, 0)
|
||||
|
||||
f.buttons.AddItem(b, tview.TaggedStringWidth(label)+4, 0, false)
|
||||
f.hasMenuButton = true
|
||||
f.group.AddElement(b)
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
// AddFormItems constructs form from data represented as a list of Item objects.
|
||||
func (f *Form) AddFormItems(items []*Item) error {
|
||||
for _, item := range items {
|
||||
formItems, e := item.createFormItems()
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
|
||||
for _, formItem := range formItems {
|
||||
f.AddFormItem(formItem)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Multiline interface represents elements that can occupy more than one line.
|
||||
|
75
internal/pkg/tui/components/formbutton.go
Normal file
75
internal/pkg/tui/components/formbutton.go
Normal file
@ -0,0 +1,75 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package components
|
||||
|
||||
import (
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/rivo/tview"
|
||||
)
|
||||
|
||||
// NewFormModalButton creates a new FormModalButton.
|
||||
func NewFormModalButton(formLabel, buttonLabel string) *FormModalButton {
|
||||
res := &FormModalButton{
|
||||
Flex: tview.NewFlex(),
|
||||
button: tview.NewButton(buttonLabel),
|
||||
label: tview.NewTextView(),
|
||||
}
|
||||
|
||||
res.label.SetText(formLabel)
|
||||
res.AddItem(res.label, 0, 1, false)
|
||||
res.AddItem(res.button, len(buttonLabel)+2, 1, false)
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
// FormModalButton the button that opens modal dialog with extended settings.
|
||||
type FormModalButton struct {
|
||||
*tview.Flex
|
||||
|
||||
label *tview.TextView
|
||||
button *tview.Button
|
||||
}
|
||||
|
||||
// SetSelectedFunc forwards that to underlying button component.
|
||||
func (b *FormModalButton) SetSelectedFunc(handler func()) *FormModalButton {
|
||||
b.button.SetSelectedFunc(handler)
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
// Focus override default focus behavior.
|
||||
func (b *FormModalButton) Focus(delegate func(tview.Primitive)) {
|
||||
b.button.Focus(delegate)
|
||||
}
|
||||
|
||||
// Blur override default blur behavior.
|
||||
func (b *FormModalButton) Blur() {
|
||||
b.button.Blur()
|
||||
}
|
||||
|
||||
// SetFormAttributes sets form attributes.
|
||||
func (b *FormModalButton) SetFormAttributes(labelWidth int, labelColor, bgColor, fieldTextColor, fieldBgColor tcell.Color) tview.FormItem {
|
||||
b.label.SetTextColor(labelColor)
|
||||
b.label.SetBackgroundColor(bgColor)
|
||||
b.SetBackgroundColor(bgColor)
|
||||
b.ResizeItem(b.label, labelWidth, 1)
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
// GetFieldWidth implements tview.FormItem.
|
||||
func (b *FormModalButton) GetFieldWidth() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
// SetFinishedFunc implements tview.FormItem.
|
||||
func (b *FormModalButton) SetFinishedFunc(handler func(key tcell.Key)) tview.FormItem {
|
||||
return b
|
||||
}
|
||||
|
||||
// GetLabel implements tview.FormItem.
|
||||
func (b *FormModalButton) GetLabel() string {
|
||||
return b.label.GetText(true)
|
||||
}
|
@ -5,6 +5,9 @@
|
||||
package components
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/rivo/tview"
|
||||
)
|
||||
|
||||
@ -21,3 +24,32 @@ func NewFormLabel(label string) *FormLabel {
|
||||
type FormLabel struct {
|
||||
*tview.TextView
|
||||
}
|
||||
|
||||
// SetFormAttributes sets form attributes.
|
||||
func (b *FormLabel) SetFormAttributes(labelWidth int, labelColor, bgColor, fieldTextColor, fieldBgColor tcell.Color) tview.FormItem {
|
||||
b.SetTextColor(labelColor)
|
||||
b.SetBackgroundColor(bgColor)
|
||||
s := strings.TrimSpace(b.GetText(false))
|
||||
|
||||
for i := 0; i < labelWidth; i++ {
|
||||
s = " " + s
|
||||
}
|
||||
b.SetText(s)
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
// GetFieldWidth implements tview.FormItem.
|
||||
func (b *FormLabel) GetFieldWidth() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
// SetFinishedFunc implements tview.FormItem.
|
||||
func (b *FormLabel) SetFinishedFunc(handler func(key tcell.Key)) tview.FormItem {
|
||||
return b
|
||||
}
|
||||
|
||||
// GetLabel implements tview.FormItem.
|
||||
func (b *FormLabel) GetLabel() string {
|
||||
return ""
|
||||
}
|
||||
|
@ -30,6 +30,17 @@ func (eg *Group) AddElement(element tview.Primitive) tview.Primitive {
|
||||
return element
|
||||
}
|
||||
|
||||
// FocusFirst sets focus to the first element.
|
||||
func (eg *Group) FocusFirst() {
|
||||
if len(eg.elements) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
eg.current = 0
|
||||
eg.focus = eg.elements[eg.current]
|
||||
eg.app.SetFocus(eg.focus)
|
||||
}
|
||||
|
||||
// NextFocus switch focus to the next element.
|
||||
func (eg *Group) NextFocus() {
|
||||
eg.detectFocus()
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
"google.golang.org/grpc"
|
||||
|
||||
"github.com/talos-systems/talos/pkg/machinery/api/machine"
|
||||
"github.com/talos-systems/talos/pkg/machinery/api/network"
|
||||
"github.com/talos-systems/talos/pkg/machinery/api/storage"
|
||||
"github.com/talos-systems/talos/pkg/machinery/client"
|
||||
)
|
||||
@ -61,6 +62,11 @@ func (c *Connection) Disks(callOptions ...grpc.CallOption) (*storage.DisksRespon
|
||||
return c.nodeClient.Disks(c.nodeCtx, callOptions...)
|
||||
}
|
||||
|
||||
// Interfaces get list of network interfaces.
|
||||
func (c *Connection) Interfaces(callOptions ...grpc.CallOption) (*network.InterfacesResponse, error) {
|
||||
return c.nodeClient.Interfaces(c.nodeCtx, callOptions...)
|
||||
}
|
||||
|
||||
// ExpandingCluster check if bootstrap node is set.
|
||||
func (c *Connection) ExpandingCluster() bool {
|
||||
return c.bootstrapClient != nil
|
||||
|
@ -9,13 +9,11 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/rivo/tview"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/talos-systems/talos/internal/pkg/tui/components"
|
||||
machineapi "github.com/talos-systems/talos/pkg/machinery/api/machine"
|
||||
@ -24,7 +22,7 @@ import (
|
||||
)
|
||||
|
||||
// NewPage creates a new installer page.
|
||||
func NewPage(name string, items ...*Item) *Page {
|
||||
func NewPage(name string, items ...*components.Item) *Page {
|
||||
return &Page{
|
||||
name: name,
|
||||
items: items,
|
||||
@ -34,156 +32,7 @@ func NewPage(name string, items ...*Item) *Page {
|
||||
// Page represents a single installer page.
|
||||
type Page struct {
|
||||
name string
|
||||
items []*Item
|
||||
}
|
||||
|
||||
// Item represents a single form item.
|
||||
type Item struct {
|
||||
name string
|
||||
description string
|
||||
dest interface{}
|
||||
options []interface{}
|
||||
}
|
||||
|
||||
// TableHeaders represents table headers list for item options which are using table representation.
|
||||
type TableHeaders []interface{}
|
||||
|
||||
// NewTableHeaders creates TableHeaders object.
|
||||
func NewTableHeaders(headers ...interface{}) TableHeaders {
|
||||
return TableHeaders(headers)
|
||||
}
|
||||
|
||||
// NewItem creates new form item.
|
||||
func NewItem(name, description string, dest interface{}, options ...interface{}) *Item {
|
||||
return &Item{
|
||||
dest: dest,
|
||||
name: name,
|
||||
description: description,
|
||||
options: options,
|
||||
}
|
||||
}
|
||||
|
||||
func (item *Item) assign(value string) error {
|
||||
// rely on yaml parser to decode value into the right type
|
||||
return yaml.Unmarshal([]byte(value), item.dest)
|
||||
}
|
||||
|
||||
// createFormItems dynamically creates tview.FormItem list based on the wrapped type.
|
||||
// nolint:gocyclo
|
||||
func (item *Item) createFormItems() ([]tview.Primitive, error) {
|
||||
res := []tview.Primitive{}
|
||||
|
||||
v := reflect.ValueOf(item.dest)
|
||||
if v.Kind() == reflect.Ptr {
|
||||
v = v.Elem()
|
||||
}
|
||||
|
||||
if item.description != "" {
|
||||
parts := strings.Split(item.description, "\n")
|
||||
for _, part := range parts {
|
||||
res = append(res, components.NewFormLabel(part))
|
||||
}
|
||||
}
|
||||
|
||||
var formItem tview.Primitive
|
||||
|
||||
// nolint:exhaustive
|
||||
switch v.Kind() {
|
||||
case reflect.Bool:
|
||||
// use checkbox for boolean fields
|
||||
checkbox := tview.NewCheckbox()
|
||||
checkbox.SetChangedFunc(func(checked bool) {
|
||||
reflect.ValueOf(item.dest).Set(reflect.ValueOf(checked))
|
||||
})
|
||||
checkbox.SetChecked(v.Bool())
|
||||
checkbox.SetLabel(item.name)
|
||||
formItem = checkbox
|
||||
default:
|
||||
if len(item.options) > 0 {
|
||||
tableHeaders, ok := item.options[0].(TableHeaders)
|
||||
if ok {
|
||||
table := components.NewTable()
|
||||
table.SetHeader(tableHeaders...)
|
||||
|
||||
data := item.options[1:]
|
||||
numColumns := len(tableHeaders)
|
||||
|
||||
if len(data)%numColumns != 0 {
|
||||
return nil, fmt.Errorf("incorrect amount of data provided for the table")
|
||||
}
|
||||
|
||||
selected := -1
|
||||
|
||||
for i := 0; i < len(data); i += numColumns {
|
||||
table.AddRow(data[i : i+numColumns]...)
|
||||
|
||||
if v.Interface() == data[i] {
|
||||
selected = i / numColumns
|
||||
}
|
||||
}
|
||||
|
||||
if selected != -1 {
|
||||
table.SelectRow(selected)
|
||||
}
|
||||
|
||||
formItem = table
|
||||
table.SetRowSelectedFunc(func(row int) {
|
||||
v.Set(reflect.ValueOf(table.GetValue(row, 0))) // always pick the first column
|
||||
})
|
||||
} else {
|
||||
dropdown := tview.NewDropDown()
|
||||
|
||||
if len(item.options)%2 != 0 {
|
||||
return nil, fmt.Errorf("wrong amount of arguments for options: should be even amount of key, value pairs")
|
||||
}
|
||||
|
||||
for i := 0; i < len(item.options); i += 2 {
|
||||
if optionName, ok := item.options[i].(string); ok {
|
||||
selected := -1
|
||||
|
||||
func(index int) {
|
||||
dropdown.AddOption(optionName, func() {
|
||||
v.Set(reflect.ValueOf(item.options[index]))
|
||||
})
|
||||
|
||||
if v.Interface() == item.options[index] {
|
||||
selected = i / 2
|
||||
}
|
||||
}(i + 1)
|
||||
|
||||
if selected != -1 {
|
||||
dropdown.SetCurrentOption(selected)
|
||||
}
|
||||
} else {
|
||||
return nil, fmt.Errorf("expected string option name, got %s", item.options[i])
|
||||
}
|
||||
}
|
||||
|
||||
dropdown.SetLabel(item.name)
|
||||
|
||||
formItem = dropdown
|
||||
}
|
||||
} else {
|
||||
input := tview.NewInputField()
|
||||
formItem = input
|
||||
input.SetLabel(item.name)
|
||||
text, err := yaml.Marshal(item.dest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
input.SetText(string(text))
|
||||
input.SetChangedFunc(func(text string) {
|
||||
if err := item.assign(text); err != nil {
|
||||
// TODO: highlight red
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
res = append(res, formItem)
|
||||
|
||||
return res, nil
|
||||
items []*components.Item
|
||||
}
|
||||
|
||||
// Installer interactive installer text based UI.
|
||||
@ -315,6 +164,7 @@ func (installer *Installer) init(conn *Connection) (err error) {
|
||||
|
||||
installer.state, err = NewState(
|
||||
installer.ctx,
|
||||
installer,
|
||||
conn,
|
||||
)
|
||||
|
||||
@ -330,11 +180,10 @@ func (installer *Installer) init(conn *Connection) (err error) {
|
||||
// nolint:gocyclo
|
||||
func (installer *Installer) configure() error {
|
||||
var (
|
||||
currentGroup *components.Group
|
||||
err error
|
||||
err error
|
||||
forms []*components.Form
|
||||
)
|
||||
|
||||
groups := []*components.Group{}
|
||||
currentPage := 0
|
||||
menuButtons := []*components.MenuButton{}
|
||||
|
||||
@ -351,21 +200,13 @@ func (installer *Installer) configure() error {
|
||||
menuButtons[currentPage].SetActive(true)
|
||||
|
||||
installer.pages.SwitchToPage(state.pages[currentPage].name)
|
||||
currentGroup = groups[currentPage]
|
||||
installer.app.SetFocus(forms[currentPage])
|
||||
}
|
||||
|
||||
capture := installer.app.GetInputCapture()
|
||||
installer.app.SetInputCapture(func(e *tcell.EventKey) *tcell.EventKey {
|
||||
if currentGroup == nil {
|
||||
return e
|
||||
}
|
||||
|
||||
//nolint:exhaustive
|
||||
switch e.Key() {
|
||||
case tcell.KeyTAB:
|
||||
currentGroup.NextFocus()
|
||||
case tcell.KeyBacktab:
|
||||
currentGroup.PrevFocus()
|
||||
case tcell.KeyCtrlN:
|
||||
setPage(currentPage + 1)
|
||||
case tcell.KeyCtrlB:
|
||||
@ -376,6 +217,8 @@ func (installer *Installer) configure() error {
|
||||
if e.Rune() >= '1' && e.Rune() < '9' {
|
||||
if e.Modifiers()&(tcell.ModAlt|tcell.ModCtrl) != 0 {
|
||||
setPage(int(e.Rune()) - 49)
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@ -410,70 +253,43 @@ func (installer *Installer) configure() error {
|
||||
}
|
||||
}
|
||||
|
||||
forms = make([]*components.Form, len(state.pages))
|
||||
|
||||
for i, p := range state.pages {
|
||||
eg := components.NewGroup(installer.app)
|
||||
groups = append(groups, eg)
|
||||
|
||||
err = func(index int) error {
|
||||
form := components.NewForm()
|
||||
form := components.NewForm(installer.app)
|
||||
form.SetBackgroundColor(color)
|
||||
forms[i] = form
|
||||
|
||||
for _, item := range p.items {
|
||||
formItems, e := item.createFormItems()
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
|
||||
for _, formItem := range formItems {
|
||||
if _, ok := formItem.(*components.FormLabel); !ok {
|
||||
eg.AddElement(formItem)
|
||||
}
|
||||
|
||||
form.AddFormItem(formItem)
|
||||
}
|
||||
if e := form.AddFormItems(p.items); e != nil {
|
||||
return e
|
||||
}
|
||||
|
||||
flex := tview.NewFlex().SetDirection(tview.FlexRow)
|
||||
flex.AddItem(form, 0, 1, false)
|
||||
|
||||
content := tview.NewFlex()
|
||||
|
||||
if index > 0 {
|
||||
back := tview.NewButton("[::u]B[::-]ack")
|
||||
back := form.AddMenuButton("[::u]B[::-]ack", false)
|
||||
back.SetSelectedFunc(func() {
|
||||
setPage(index - 1)
|
||||
})
|
||||
|
||||
content.AddItem(eg.AddElement(back), 10, 1, false)
|
||||
}
|
||||
|
||||
addMenuItem(p.name, index)
|
||||
|
||||
content.AddItem(tview.NewBox().SetBackgroundColor(color), 0, 1, false)
|
||||
flex.SetBackgroundColor(color)
|
||||
form.SetBackgroundColor(color)
|
||||
content.SetBackgroundColor(color)
|
||||
|
||||
if index < len(state.pages)-1 {
|
||||
next := tview.NewButton("[::u]N[::-]ext")
|
||||
next := form.AddMenuButton("[::u]N[::-]ext", index == 0)
|
||||
next.SetSelectedFunc(func() {
|
||||
setPage(index + 1)
|
||||
})
|
||||
|
||||
content.AddItem(eg.AddElement(next), 10, 1, false)
|
||||
} else {
|
||||
install := tview.NewButton("Install")
|
||||
install := form.AddMenuButton("Install", false)
|
||||
install.SetBackgroundColor(tcell.ColorGreen)
|
||||
install.SetTitleAlign(tview.AlignCenter)
|
||||
install.SetSelectedFunc(func() {
|
||||
close(done)
|
||||
})
|
||||
content.AddItem(eg.AddElement(install), 11, 1, false)
|
||||
}
|
||||
|
||||
flex.AddItem(content, 1, 1, false)
|
||||
|
||||
installer.addPage(p.name, flex, index == 0, menu)
|
||||
installer.addPage(p.name, form, index == 0, menu)
|
||||
|
||||
return nil
|
||||
}(i)
|
||||
@ -698,16 +514,22 @@ func (installer *Installer) addPage(name string, primitive tview.Primitive, swit
|
||||
frame := tview.NewFrame(content).SetBorders(1, 1, 1, 1, 2, 2).
|
||||
AddText(name, true, tview.AlignLeft, tcell.ColorWhite).
|
||||
AddText("Talos Interactive Installer", true, tview.AlignCenter, tcell.ColorWhite).
|
||||
AddText(version.Tag, true, tview.AlignRight, tcell.ColorIvory)
|
||||
AddText(version.Tag, true, tview.AlignRight, tcell.ColorIvory).
|
||||
AddText("<CTRL>+B/<CTRL>+N to switch tabs", false, tview.AlignLeft, tcell.ColorIvory).
|
||||
AddText("<TAB> for navigation", false, tview.AlignLeft, tcell.ColorIvory).
|
||||
AddText("[::b]Key Bindings[::-]", false, tview.AlignLeft, tcell.ColorIvory)
|
||||
|
||||
frame.SetBackgroundColor(frameBGColor)
|
||||
|
||||
installer.pages.AddPage(name,
|
||||
frame, true, false)
|
||||
installer.app.Draw()
|
||||
}
|
||||
|
||||
if switchToPage {
|
||||
if switchToPage {
|
||||
installer.pages.AddAndSwitchToPage(name, frame, true)
|
||||
} else {
|
||||
installer.pages.AddPage(name,
|
||||
frame, true, false)
|
||||
}
|
||||
} else if switchToPage {
|
||||
installer.pages.SwitchToPage(name)
|
||||
}
|
||||
|
||||
installer.app.ForceDraw()
|
||||
}
|
||||
|
@ -8,13 +8,17 @@ package installer
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/dustin/go-humanize"
|
||||
"github.com/rivo/tview"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/talos-systems/talos/internal/pkg/tui/components"
|
||||
"github.com/talos-systems/talos/pkg/images"
|
||||
machineapi "github.com/talos-systems/talos/pkg/machinery/api/machine"
|
||||
"github.com/talos-systems/talos/pkg/machinery/api/network"
|
||||
"github.com/talos-systems/talos/pkg/machinery/config/types/v1alpha1"
|
||||
"github.com/talos-systems/talos/pkg/machinery/config/types/v1alpha1/machine"
|
||||
"github.com/talos-systems/talos/pkg/machinery/constants"
|
||||
@ -31,7 +35,8 @@ var cniPresets = map[string]*machineapi.CNIConfig{
|
||||
}
|
||||
|
||||
// NewState creates new installer state.
|
||||
func NewState(ctx context.Context, conn *Connection) (*State, error) {
|
||||
// nolint:gocyclo
|
||||
func NewState(ctx context.Context, installer *Installer, conn *Connection) (*State, error) {
|
||||
opts := &machineapi.GenerateConfigurationRequest{
|
||||
ConfigVersion: "v1alpha1",
|
||||
MachineConfig: &machineapi.MachineConfig{
|
||||
@ -58,7 +63,7 @@ func NewState(ctx context.Context, conn *Connection) (*State, error) {
|
||||
}
|
||||
|
||||
installDiskOptions := []interface{}{
|
||||
NewTableHeaders("device name", "model name", "size"),
|
||||
components.NewTableHeaders("DEVICE NAME", "MODEL NAME", "SIZE"),
|
||||
}
|
||||
|
||||
disks, err := conn.Disks()
|
||||
@ -78,13 +83,13 @@ func NewState(ctx context.Context, conn *Connection) (*State, error) {
|
||||
|
||||
if conn.ExpandingCluster() {
|
||||
machineTypes = []interface{}{
|
||||
"worker", machineapi.MachineConfig_MachineType(machine.TypeJoin),
|
||||
"control plane", machineapi.MachineConfig_MachineType(machine.TypeControlPlane),
|
||||
" worker ", machineapi.MachineConfig_MachineType(machine.TypeJoin),
|
||||
" control plane ", machineapi.MachineConfig_MachineType(machine.TypeControlPlane),
|
||||
}
|
||||
opts.MachineConfig.Type = machineapi.MachineConfig_MachineType(machine.TypeControlPlane)
|
||||
} else {
|
||||
machineTypes = []interface{}{
|
||||
"control plane", machineapi.MachineConfig_MachineType(machine.TypeInit),
|
||||
" control plane ", machineapi.MachineConfig_MachineType(machine.TypeInit),
|
||||
}
|
||||
}
|
||||
|
||||
@ -94,64 +99,96 @@ func NewState(ctx context.Context, conn *Connection) (*State, error) {
|
||||
opts: opts,
|
||||
}
|
||||
|
||||
networkConfigItems := []*Item{
|
||||
NewItem(
|
||||
"hostname",
|
||||
networkConfigItems := []*components.Item{
|
||||
components.NewItem(
|
||||
"Hostname",
|
||||
v1alpha1.NetworkConfigDoc.Describe("hostname", true),
|
||||
&opts.MachineConfig.NetworkConfig.Hostname,
|
||||
),
|
||||
NewItem(
|
||||
"dns domain",
|
||||
components.NewItem(
|
||||
"DNS Domain",
|
||||
v1alpha1.ClusterNetworkConfigDoc.Describe("dnsDomain", true),
|
||||
&opts.ClusterConfig.ClusterNetwork.DnsDomain,
|
||||
),
|
||||
}
|
||||
|
||||
if !conn.ExpandingCluster() {
|
||||
networkConfigItems = append(networkConfigItems, NewItem(
|
||||
"type",
|
||||
v1alpha1.ClusterNetworkConfigDoc.Describe("cni", true),
|
||||
&state.cni,
|
||||
NewTableHeaders("CNI", "description"),
|
||||
constants.DefaultCNI, "CNI used by Talos by default",
|
||||
"cilium", "Cillium 1.8 installed through quick-install.yaml",
|
||||
interfaces, err := conn.Interfaces()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
addedInterfaces := false
|
||||
opts.MachineConfig.NetworkConfig.Interfaces = []*machineapi.NetworkDeviceConfig{}
|
||||
|
||||
for _, iface := range interfaces.Messages[0].Interfaces {
|
||||
status := ""
|
||||
|
||||
if (net.Flags(iface.Flags) & net.FlagUp) != 0 {
|
||||
status = " (UP)"
|
||||
}
|
||||
|
||||
if !addedInterfaces {
|
||||
networkConfigItems = append(networkConfigItems, components.NewSeparator("Network Interfaces Configuration"))
|
||||
addedInterfaces = true
|
||||
}
|
||||
|
||||
networkConfigItems = append(networkConfigItems, components.NewItem(
|
||||
fmt.Sprintf("%s, %s%s", iface.Name, iface.Hardwareaddr, status),
|
||||
"",
|
||||
configureAdapter(installer, opts, iface),
|
||||
))
|
||||
}
|
||||
|
||||
if !conn.ExpandingCluster() {
|
||||
networkConfigItems = append(networkConfigItems,
|
||||
components.NewSeparator(v1alpha1.ClusterNetworkConfigDoc.Describe("cni", true)),
|
||||
components.NewItem(
|
||||
"Type",
|
||||
v1alpha1.ClusterNetworkConfigDoc.Describe("cni", true),
|
||||
&state.cni,
|
||||
components.NewTableHeaders("CNI", "description"),
|
||||
constants.DefaultCNI, "CNI used by Talos by default",
|
||||
"cilium", "Cillium 1.8 installed through quick-install.yaml",
|
||||
))
|
||||
}
|
||||
|
||||
state.pages = []*Page{
|
||||
NewPage("Installer Params",
|
||||
NewItem(
|
||||
"image",
|
||||
components.NewItem(
|
||||
"Image",
|
||||
v1alpha1.InstallConfigDoc.Describe("image", true),
|
||||
&opts.MachineConfig.InstallConfig.InstallImage,
|
||||
),
|
||||
NewItem(
|
||||
"install disk",
|
||||
components.NewSeparator(
|
||||
v1alpha1.InstallConfigDoc.Describe("disk", true),
|
||||
),
|
||||
components.NewItem(
|
||||
"Install Disk",
|
||||
"",
|
||||
&opts.MachineConfig.InstallConfig.InstallDisk,
|
||||
installDiskOptions...,
|
||||
),
|
||||
),
|
||||
NewPage("Machine Config",
|
||||
NewItem(
|
||||
"machine type",
|
||||
components.NewItem(
|
||||
"Machine Type",
|
||||
v1alpha1.MachineConfigDoc.Describe("type", true),
|
||||
&opts.MachineConfig.Type,
|
||||
machineTypes...,
|
||||
),
|
||||
NewItem(
|
||||
"cluster name",
|
||||
components.NewItem(
|
||||
"Cluster Name",
|
||||
v1alpha1.ClusterConfigDoc.Describe("clusterName", true),
|
||||
&opts.ClusterConfig.Name,
|
||||
),
|
||||
NewItem(
|
||||
"control plane endpoint",
|
||||
components.NewItem(
|
||||
"Control Plane Endpoint",
|
||||
v1alpha1.ControlPlaneConfigDoc.Describe("endpoint", true),
|
||||
&opts.ClusterConfig.ControlPlane.Endpoint,
|
||||
),
|
||||
NewItem(
|
||||
"kubernetes version",
|
||||
"Kubernetes version to install.",
|
||||
components.NewItem(
|
||||
"Kubernetes Version",
|
||||
"",
|
||||
&opts.MachineConfig.KubernetesVersion,
|
||||
),
|
||||
),
|
||||
@ -182,3 +219,104 @@ func (s *State) GenConfig() (*machineapi.GenerateConfigurationResponse, error) {
|
||||
|
||||
return s.conn.GenerateConfiguration(s.opts)
|
||||
}
|
||||
|
||||
func configureAdapter(installer *Installer, opts *machineapi.GenerateConfigurationRequest, adapter *network.Interface) func(item *components.Item) tview.Primitive {
|
||||
return func(item *components.Item) tview.Primitive {
|
||||
return components.NewFormModalButton(item.Name, "configure").
|
||||
SetSelectedFunc(func() {
|
||||
deviceIndex := -1
|
||||
var adapterSettings *machineapi.NetworkDeviceConfig
|
||||
|
||||
for i, iface := range opts.MachineConfig.NetworkConfig.Interfaces {
|
||||
if iface.Interface == adapter.Name {
|
||||
deviceIndex = i
|
||||
adapterSettings = iface
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if adapterSettings == nil {
|
||||
adapterSettings = &machineapi.NetworkDeviceConfig{
|
||||
Interface: adapter.Name,
|
||||
Dhcp: true,
|
||||
Mtu: int32(adapter.Mtu),
|
||||
Ignore: false,
|
||||
DhcpOptions: &machineapi.DHCPOptionsConfig{},
|
||||
}
|
||||
|
||||
if len(adapter.Ipaddress) > 0 {
|
||||
adapterSettings.Cidr = adapter.Ipaddress[0]
|
||||
}
|
||||
}
|
||||
|
||||
items := []*components.Item{
|
||||
components.NewItem(
|
||||
"Use DHCP",
|
||||
v1alpha1.DeviceDoc.Describe("dhcp", true),
|
||||
&adapterSettings.Dhcp,
|
||||
),
|
||||
components.NewItem(
|
||||
"Ignore",
|
||||
v1alpha1.DeviceDoc.Describe("ignore", true),
|
||||
&adapterSettings.Ignore,
|
||||
),
|
||||
components.NewItem(
|
||||
"CIDR",
|
||||
v1alpha1.DeviceDoc.Describe("cidr", true),
|
||||
&adapterSettings.Cidr,
|
||||
),
|
||||
components.NewItem(
|
||||
"MTU",
|
||||
v1alpha1.DeviceDoc.Describe("mtu", true),
|
||||
&adapterSettings.Mtu,
|
||||
),
|
||||
components.NewItem(
|
||||
"Route Metric",
|
||||
v1alpha1.DeviceDoc.Describe("dhcpOptions", true),
|
||||
&adapterSettings.DhcpOptions.RouteMetric,
|
||||
),
|
||||
}
|
||||
|
||||
adapterConfiguration := components.NewForm(installer.app)
|
||||
if err := adapterConfiguration.AddFormItems(items); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
focused := installer.app.GetFocus()
|
||||
page, _ := installer.pages.GetFrontPage()
|
||||
|
||||
goBack := func() {
|
||||
installer.pages.SwitchToPage(page)
|
||||
installer.app.SetFocus(focused)
|
||||
}
|
||||
|
||||
adapterConfiguration.AddMenuButton("Cancel", false).SetSelectedFunc(func() {
|
||||
goBack()
|
||||
})
|
||||
|
||||
adapterConfiguration.AddMenuButton("Apply", false).SetSelectedFunc(func() {
|
||||
goBack()
|
||||
|
||||
if deviceIndex == -1 {
|
||||
opts.MachineConfig.NetworkConfig.Interfaces = append(
|
||||
opts.MachineConfig.NetworkConfig.Interfaces,
|
||||
adapterSettings,
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
flex := tview.NewFlex().SetDirection(tview.FlexRow)
|
||||
flex.AddItem(tview.NewBox().SetBackgroundColor(color), 1, 0, false)
|
||||
flex.AddItem(adapterConfiguration, 0, 1, false)
|
||||
|
||||
installer.addPage(
|
||||
fmt.Sprintf("Adapter %s Configuration", adapter.Name),
|
||||
flex,
|
||||
true,
|
||||
nil,
|
||||
)
|
||||
installer.app.SetFocus(adapterConfiguration)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -36,6 +36,7 @@ title: API
|
||||
- [ContainersResponse](#machine.ContainersResponse)
|
||||
- [ControlPlaneConfig](#machine.ControlPlaneConfig)
|
||||
- [CopyRequest](#machine.CopyRequest)
|
||||
- [DHCPOptionsConfig](#machine.DHCPOptionsConfig)
|
||||
- [DiskStat](#machine.DiskStat)
|
||||
- [DiskStats](#machine.DiskStats)
|
||||
- [DiskStatsResponse](#machine.DiskStatsResponse)
|
||||
@ -72,6 +73,7 @@ title: API
|
||||
- [MountsResponse](#machine.MountsResponse)
|
||||
- [NetDev](#machine.NetDev)
|
||||
- [NetworkConfig](#machine.NetworkConfig)
|
||||
- [NetworkDeviceConfig](#machine.NetworkDeviceConfig)
|
||||
- [NetworkDeviceStats](#machine.NetworkDeviceStats)
|
||||
- [NetworkDeviceStatsResponse](#machine.NetworkDeviceStatsResponse)
|
||||
- [PhaseEvent](#machine.PhaseEvent)
|
||||
@ -95,6 +97,7 @@ title: API
|
||||
- [Rollback](#machine.Rollback)
|
||||
- [RollbackRequest](#machine.RollbackRequest)
|
||||
- [RollbackResponse](#machine.RollbackResponse)
|
||||
- [RouteConfig](#machine.RouteConfig)
|
||||
- [SequenceEvent](#machine.SequenceEvent)
|
||||
- [ServiceEvent](#machine.ServiceEvent)
|
||||
- [ServiceEvents](#machine.ServiceEvents)
|
||||
@ -651,6 +654,21 @@ Copy produces .tar.gz archive which is streamed back to the caller
|
||||
|
||||
|
||||
|
||||
<a name="machine.DHCPOptionsConfig"></a>
|
||||
|
||||
### DHCPOptionsConfig
|
||||
|
||||
|
||||
|
||||
| Field | Type | Label | Description |
|
||||
| ----- | ---- | ----- | ----------- |
|
||||
| route_metric | [uint32](#uint32) | | |
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<a name="machine.DiskStat"></a>
|
||||
|
||||
### DiskStat
|
||||
@ -1300,6 +1318,28 @@ The messages message containing the requested df stats.
|
||||
| Field | Type | Label | Description |
|
||||
| ----- | ---- | ----- | ----------- |
|
||||
| hostname | [string](#string) | | |
|
||||
| interfaces | [NetworkDeviceConfig](#machine.NetworkDeviceConfig) | repeated | |
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<a name="machine.NetworkDeviceConfig"></a>
|
||||
|
||||
### NetworkDeviceConfig
|
||||
|
||||
|
||||
|
||||
| Field | Type | Label | Description |
|
||||
| ----- | ---- | ----- | ----------- |
|
||||
| interface | [string](#string) | | |
|
||||
| cidr | [string](#string) | | |
|
||||
| mtu | [int32](#int32) | | |
|
||||
| dhcp | [bool](#bool) | | |
|
||||
| ignore | [bool](#bool) | | |
|
||||
| dhcp_options | [DHCPOptionsConfig](#machine.DHCPOptionsConfig) | | |
|
||||
| routes | [RouteConfig](#machine.RouteConfig) | repeated | |
|
||||
|
||||
|
||||
|
||||
@ -1660,6 +1700,23 @@ rpc rollback
|
||||
|
||||
|
||||
|
||||
<a name="machine.RouteConfig"></a>
|
||||
|
||||
### RouteConfig
|
||||
|
||||
|
||||
|
||||
| Field | Type | Label | Description |
|
||||
| ----- | ---- | ----- | ----------- |
|
||||
| network | [string](#string) | | |
|
||||
| gateway | [string](#string) | | |
|
||||
| metric | [uint32](#uint32) | | |
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<a name="machine.SequenceEvent"></a>
|
||||
|
||||
### SequenceEvent
|
||||
|
Loading…
x
Reference in New Issue
Block a user