diff --git a/cmd/incus/operation.go b/cmd/incus/operation.go index dd7238830e..736a14fbdb 100644 --- a/cmd/incus/operation.go +++ b/cmd/incus/operation.go @@ -17,6 +17,11 @@ type cmdOperation struct { global *cmdGlobal } +type operationColumn struct { + Name string + Data func(api.Operation) string +} + func (c *cmdOperation) Command() *cobra.Command { cmd := &cobra.Command{} cmd.Use = usage("operation") @@ -121,13 +126,13 @@ or (extended) config keys. Commas between consecutive shorthand chars are optional. Pre-defined column shorthand chars: - i - iD - t - type - d - description - s - state - c - cancelable + i - ID + t - Type + d - Description + s - State + c - Cancelable C - Created - l - location`)) + l - Location`)) cmd.Flags().StringVarP(&c.flagFormat, "format", "f", "table", i18n.G("Format (csv|json|table|yaml|compact)")+"``") cmd.Flags().BoolVar(&c.flagAllProjects, "all-projects", false, i18n.G("List operations from all projects")+"``") cmd.Flags().StringVarP(&c.flagColumns, "columns", "c", defaultOperationColumns, i18n.G("Columns")+"``") @@ -139,6 +144,73 @@ Pre-defined column shorthand chars: const defaultOperationColumns = "itdscCl" +func (c *cmdOperationList) parseColumns() ([]operationColumn, error) { + columnsShorthandMap := map[rune]operationColumn{ + 'i': {i18n.G("ID"), c.operationIDcolumnData}, + 't': {i18n.G("TYPE"), c.typeColumnData}, + 'd': {i18n.G("DESCRIPTION"), c.descriptionColumnData}, + 's': {i18n.G("STATE"), c.stateColumnData}, + 'c': {i18n.G("CANCELABLE"), c.cancelableColumnData}, + 'C': {i18n.G("CREATED"), c.createdColumnData}, + 'l': {i18n.G("LOCATION"), c.locationColumnData}, + } + + columnList := strings.Split(c.flagColumns, ",") + + columns := []operationColumn{} + + for _, columnEntry := range columnList { + if columnEntry == "" { + return nil, fmt.Errorf(i18n.G("Empty column entry (redundant, leading or trailing command) in '%s'"), c.flagColumns) + } + + for _, columnRune := range columnEntry { + column, ok := columnsShorthandMap[columnRune] + if !ok { + return nil, fmt.Errorf(i18n.G("Unknown column shorthand char '%c' in '%s'"), columnRune, columnEntry) + } + + columns = append(columns, column) + } + } + + return columns, nil +} + +func (c *cmdOperationList) operationIDcolumnData(op api.Operation) string { + return op.ID +} + +func (c *cmdOperationList) typeColumnData(op api.Operation) string { + return strings.ToUpper(op.Class) +} + +func (c *cmdOperationList) descriptionColumnData(op api.Operation) string { + return op.Description +} + +func (c *cmdOperationList) stateColumnData(op api.Operation) string { + return strings.ToUpper(op.Status) +} + +func (c *cmdOperationList) cancelableColumnData(op api.Operation) string { + strCancelable := i18n.G("NO") + + if op.MayCancel { + strCancelable = i18n.G("YES") + } + + return strCancelable +} + +func (c *cmdOperationList) createdColumnData(op api.Operation) string { + return op.CreatedAt.Local().Format(dateLayout) +} + +func (c *cmdOperationList) locationColumnData(op api.Operation) string { + return op.Location +} + func (c *cmdOperationList) Run(cmd *cobra.Command, args []string) error { // Quick checks. exit, err := c.global.CheckArgs(cmd, args, 0, 1) @@ -174,33 +246,29 @@ func (c *cmdOperationList) Run(cmd *cobra.Command, args []string) error { return err } + // Parse column flags. + columns, err := c.parseColumns() + if err != nil { + return err + } + // Render the table data := [][]string{} for _, op := range operations { - cancelable := i18n.G("NO") - if op.MayCancel { - cancelable = i18n.G("YES") - } + line := []string{} + for _, column := range columns { + line = append(line, column.Data(op)) - entry := []string{op.ID, strings.ToUpper(op.Class), op.Description, strings.ToUpper(op.Status), cancelable, op.CreatedAt.Local().Format(dateLayout)} - if resource.server.IsClustered() { - entry = append(entry, op.Location) } - data = append(data, entry) + data = append(data, line) } sort.Sort(cli.SortColumnsNaturally(data)) - header := []string{ - i18n.G("ID"), - i18n.G("TYPE"), - i18n.G("DESCRIPTION"), - i18n.G("STATE"), - i18n.G("CANCELABLE"), - i18n.G("CREATED")} - if resource.server.IsClustered() { - header = append(header, i18n.G("LOCATION")) + header := []string{} + for _, column := range columns { + header = append(header, column.Name) } return cli.RenderTable(c.flagFormat, header, data, operations)