package config import ( "encoding/json" "os" "path/filepath" "strings" "github.com/BurntSushi/toml" ) type AppColors struct { Borders Borders `toml:"borders,omitempty" json:"borders,omitempty"` ChartCPU ChartCPU `toml:"chart_cpu,omitempty" json:"chart_cpu,omitempty"` ChartMemory ChartMemory `toml:"chart_memory,omitempty" json:"chart_memory,omitempty"` ChartBandwidth ChartBandwidth `toml:"chart_bandwidth,omitempty" json:"chart_bandwidth,omitempty"` ChartPorts ChartPorts `toml:"chart_ports,omitempty" json:"chart_ports,omitempty"` Commands Commands `toml:"commands,omitempty" json:"commands,omitempty"` ContainerState ContainerState `toml:"container_state,omitempty" json:"container_state,omitempty"` Containers Containers `toml:"containers,omitempty" json:"containers,omitempty"` LogSearch LogSearch `toml:"log_search,omitempty" json:"log_search,omitempty"` Filter Filter `toml:"filter,omitempty" json:"filter,omitempty"` HeadersBar HeadersBar `toml:"headers_bar,omitempty" json:"headers_bar,omitempty"` Logs Logs `toml:"logs,omitempty" json:"logs,omitempty"` PopupDelete PopupDelete `toml:"popup_delete,omitempty" json:"popup_delete,omitempty"` PopupError PopupError `toml:"popup_error,omitempty" json:"popup_error,omitempty"` PopupHelp PopupHelp `toml:"popup_help,omitempty" json:"popup_help,omitempty"` PopupInfo PopupInfo `toml:"popup_info,omitempty" json:"popup_info,omitempty"` } type Borders struct { Selected string `toml:"selected,omitempty" json:"selected,omitempty"` Unselected string `toml:"unselected,omitempty" json:"unselected,omitempty"` } type ChartCPU struct { Background string `toml:"background,omitempty" json:"background,omitempty"` Border string `toml:"border,omitempty" json:"border,omitempty"` Title string `toml:"title,omitempty" json:"title,omitempty"` Max string `toml:"max,omitempty" json:"max,omitempty"` Points string `toml:"points,omitempty" json:"points,omitempty"` YAxis string `toml:"y_axis,omitempty" json:"y_axis,omitempty"` } type ChartMemory struct { Background string `toml:"background,omitempty" json:"background,omitempty"` Border string `toml:"border,omitempty" json:"border,omitempty"` Title string `toml:"title,omitempty" json:"title,omitempty"` Max string `toml:"max,omitempty" json:"max,omitempty"` Points string `toml:"points,omitempty" json:"points,omitempty"` YAxis string `toml:"y_axis,omitempty" json:"y_axis,omitempty"` } type ChartBandwidth struct { Background string `toml:"background,omitempty" json:"background,omitempty"` Border string `toml:"border,omitempty" json:"border,omitempty"` MaxRX string `toml:"max_rx,omitempty" json:"max_rx,omitempty"` MaxTX string `toml:"max_tx,omitempty" json:"max_tx,omitempty"` PointsRX string `toml:"points_rx,omitempty" json:"points_rx,omitempty"` PointsTX string `toml:"points_tx,omitempty" json:"points_tx,omitempty"` TitleRX string `toml:"title_rx,omitempty" json:"title_rx,omitempty"` TitleTX string `toml:"title_tx,omitempty" json:"title_tx,omitempty"` YAxis string `toml:"y_axis,omitempty" json:"y_axis,omitempty"` } type ChartPorts struct { Background string `toml:"background,omitempty" json:"background,omitempty"` Border string `toml:"border,omitempty" json:"border,omitempty"` Title string `toml:"title,omitempty" json:"title,omitempty"` Headings string `toml:"headings,omitempty" json:"headings,omitempty"` Text string `toml:"text,omitempty" json:"text,omitempty"` } type Commands struct { Background string `toml:"background,omitempty" json:"background,omitempty"` Pause string `toml:"pause,omitempty" json:"pause,omitempty"` Restart string `toml:"restart,omitempty" json:"restart,omitempty"` Stop string `toml:"stop,omitempty" json:"stop,omitempty"` Delete string `toml:"delete,omitempty" json:"delete,omitempty"` Resume string `toml:"resume,omitempty" json:"resume,omitempty"` Start string `toml:"start,omitempty" json:"start,omitempty"` } type ContainerState struct { Dead string `toml:"dead,omitempty" json:"dead,omitempty"` Exited string `toml:"exited,omitempty" json:"exited,omitempty"` Paused string `toml:"paused,omitempty" json:"paused,omitempty"` Removing string `toml:"removing,omitempty" json:"removing,omitempty"` Restarting string `toml:"restarting,omitempty" json:"restarting,omitempty"` RunningHealthy string `toml:"running_healthy,omitempty" json:"running_healthy,omitempty"` RunningUnhealthy string `toml:"running_unhealthy,omitempty" json:"running_unhealthy,omitempty"` Unknown string `toml:"unknown,omitempty" json:"unknown,omitempty"` } type Containers struct { Background string `toml:"background,omitempty" json:"background,omitempty"` Icon string `toml:"icon,omitempty" json:"icon,omitempty"` Text string `toml:"text,omitempty" json:"text,omitempty"` TextRX string `toml:"text_rx,omitempty" json:"text_rx,omitempty"` TextTX string `toml:"text_tx,omitempty" json:"text_tx,omitempty"` } type LogSearch struct { Background string `toml:"background,omitempty" json:"background,omitempty"` Text string `toml:"text,omitempty" json:"text,omitempty"` ButtonText string `toml:"button_text,omitempty" json:"button_text,omitempty"` Highlight string `toml:"highlight,omitempty" json:"highlight,omitempty"` } type Filter struct { Background string `toml:"background,omitempty" json:"background,omitempty"` Text string `toml:"text,omitempty" json:"text,omitempty"` SelectedFilterBackground string `toml:"selected_filter_background,omitempty" json:"selected_filter_background,omitempty"` SelectedFilterText string `toml:"selected_filter_text,omitempty" json:"selected_filter_text,omitempty"` Highlight string `toml:"highlight,omitempty" json:"highlight,omitempty"` } type HeadersBar struct { Background string `toml:"background,omitempty" json:"background,omitempty"` LoadingSpinner string `toml:"loading_spinner,omitempty" json:"loading_spinner,omitempty"` Text string `toml:"text,omitempty" json:"text,omitempty"` TextSelected string `toml:"text_selected,omitempty" json:"text_selected,omitempty"` } type Logs struct { Background string `toml:"background,omitempty" json:"background,omitempty"` Text string `toml:"text,omitempty" json:"text,omitempty"` } type PopupDelete struct { Background string `toml:"background,omitempty" json:"background,omitempty"` Text string `toml:"text,omitempty" json:"text,omitempty"` TextHighlight string `toml:"text_highlight,omitempty" json:"text_highlight,omitempty"` } type PopupError struct { Background string `toml:"background,omitempty" json:"background,omitempty"` Text string `toml:"text,omitempty" json:"text,omitempty"` } type PopupHelp struct { Background string `toml:"background,omitempty" json:"background,omitempty"` Text string `toml:"text,omitempty" json:"text,omitempty"` TextHighlight string `toml:"text_highlight,omitempty" json:"text_highlight,omitempty"` } type PopupInfo struct { Background string `toml:"background,omitempty" json:"background,omitempty"` Text string `toml:"text,omitempty" json:"text,omitempty"` } type Keymap struct { Clear []string `toml:"clear,omitempty" json:"clear,omitempty"` ClearSecondary []string `toml:"-" json:"-"` DeleteConfirm []string `toml:"delete_confirm,omitempty" json:"delete_confirm,omitempty"` DeleteConfirmSecondary []string `toml:"-" json:"-"` DeleteDeny []string `toml:"delete_deny,omitempty" json:"delete_deny,omitempty"` DeleteDenySecondary []string `toml:"-" json:"-"` Exec []string `toml:"exec,omitempty" json:"exec,omitempty"` ExecSecondary []string `toml:"-" json:"-"` FilterMode []string `toml:"filter_mode,omitempty" json:"filter_mode,omitempty"` FilterModeSecondary []string `toml:"-" json:"-"` Inspect []string `toml:"inspect,omitempty" json:"inspect,omitempty"` InspectSecondary []string `toml:"-" json:"-"` ForceRedraw []string `toml:"force_redraw,omitempty" json:"force_redraw,omitempty"` ForceRedrawSecondary []string `toml:"-" json:"-"` ScrollBack []string `toml:"scroll_back,omitempty" json:"scroll_back,omitempty"` ScrollBackSecondary []string `toml:"-" json:"-"` ScrollForward []string `toml:"scroll_forward,omitempty" json:"scroll_forward,omitempty"` ScrollForwardSecondary []string `toml:"-" json:"-"` LogSearchMode []string `toml:"log_search_mode,omitempty" json:"log_search_mode,omitempty"` LogSearchModeSecondary []string `toml:"-" json:"-"` LogSectionHeightDecrease []string `toml:"log_section_height_decrease,omitempty" json:"log_section_height_decrease,omitempty"` LogSectionHeightDecreaseSecondary []string `toml:"-" json:"-"` LogSectionHeightIncrease []string `toml:"log_section_height_increase,omitempty" json:"log_section_height_increase,omitempty"` LogSectionHeightIncreaseSecondary []string `toml:"-" json:"-"` LogSectionToggle []string `toml:"log_section_toggle,omitempty" json:"log_section_toggle,omitempty"` LogSectionToggleSecondary []string `toml:"-" json:"-"` Quit []string `toml:"quit,omitempty" json:"quit,omitempty"` QuitSecondary []string `toml:"-" json:"-"` SaveLogs []string `toml:"save_logs,omitempty" json:"save_logs,omitempty"` SaveLogsSecondary []string `toml:"-" json:"-"` ScrollDown []string `toml:"scroll_down,omitempty" json:"scroll_down,omitempty"` ScrollDownSecondary []string `toml:"-" json:"-"` ScrollEnd []string `toml:"scroll_end,omitempty" json:"scroll_end,omitempty"` ScrollEndSecondary []string `toml:"-" json:"-"` ScrollStart []string `toml:"scroll_start,omitempty" json:"scroll_start,omitempty"` ScrollStartSecondary []string `toml:"-" json:"-"` ScrollUp []string `toml:"scroll_up,omitempty" json:"scroll_up,omitempty"` ScrollUpSecondary []string `toml:"-" json:"-"` SelectNextPanel []string `toml:"select_next_panel,omitempty" json:"select_next_panel,omitempty"` SelectNextPanelSecondary []string `toml:"-" json:"-"` SelectPreviousPanel []string `toml:"select_previous_panel,omitempty" json:"select_previous_panel,omitempty"` SelectPreviousPanelSecondary []string `toml:"-" json:"-"` SortByName []string `toml:"sort_by_name,omitempty" json:"sort_by_name,omitempty"` SortByNameSecondary []string `toml:"-" json:"-"` SortByState []string `toml:"sort_by_state,omitempty" json:"sort_by_state,omitempty"` SortByStateSecondary []string `toml:"-" json:"-"` SortByStatus []string `toml:"sort_by_status,omitempty" json:"sort_by_status,omitempty"` SortByStatusSecondary []string `toml:"-" json:"-"` SortByCPU []string `toml:"sort_by_cpu,omitempty" json:"sort_by_cpu,omitempty"` SortByCPUSecondary []string `toml:"-" json:"-"` SortByMemory []string `toml:"sort_by_memory,omitempty" json:"sort_by_memory,omitempty"` SortByMemorySecondary []string `toml:"-" json:"-"` SortByID []string `toml:"sort_by_id,omitempty" json:"sort_by_id,omitempty"` SortByIDSecondary []string `toml:"-" json:"-"` SortByImage []string `toml:"sort_by_image,omitempty" json:"sort_by_image,omitempty"` SortByImageSecondary []string `toml:"-" json:"-"` SortByRX []string `toml:"sort_by_rx,omitempty" json:"sort_by_rx,omitempty"` SortByRXSecondary []string `toml:"-" json:"-"` SortByTX []string `toml:"sort_by_tx,omitempty" json:"sort_by_tx,omitempty"` SortByTXSecondary []string `toml:"-" json:"-"` SortReset []string `toml:"sort_reset,omitempty" json:"sort_reset,omitempty"` SortResetSecondary []string `toml:"-" json:"-"` ToggleHelp []string `toml:"toggle_help,omitempty" json:"toggle_help,omitempty"` ToggleHelpSecondary []string `toml:"-" json:"-"` ToggleMouseCapture []string `toml:"toggle_mouse_capture,omitempty" json:"toggle_mouse_capture,omitempty"` ToggleMouseCaptureSecondary []string `toml:"-" json:"-"` LogSectionToggleAlt []string `toml:"-" json:"-"` ScrollMany string `toml:"scroll_many,omitempty" json:"scroll_many,omitempty"` } type Config struct { AppColors AppColors `toml:"colors,omitempty" json:"colors,omitempty"` ColorLogs bool `toml:"color_logs,omitempty" json:"color_logs,omitempty"` DirSave string `toml:"save_dir,omitempty" json:"save_dir,omitempty"` DirConfig string `toml:"-" json:"-"` DockerIntervalMs int `toml:"docker_interval,omitempty" json:"docker_interval,omitempty"` GUI bool `toml:"gui,omitempty" json:"gui,omitempty"` Host string `toml:"host,omitempty" json:"host,omitempty"` InContainer bool `toml:"-" json:"-"` Keymap Keymap `toml:"keymap,omitempty" json:"keymap,omitempty"` LogSearchCaseSensitive bool `toml:"log_search_case_sensitive,omitempty" json:"log_search_case_sensitive,omitempty"` RawLogs bool `toml:"raw_logs,omitempty" json:"raw_logs,omitempty"` ShowLogs bool `toml:"show_logs,omitempty" json:"show_logs,omitempty"` ShowSelf bool `toml:"show_self,omitempty" json:"show_self,omitempty"` ShowStdErr bool `toml:"show_std_err,omitempty" json:"show_std_err,omitempty"` ShowTimestamp bool `toml:"show_timestamp,omitempty" json:"show_timestamp,omitempty"` TimestampFormat string `toml:"timestamp_format,omitempty" json:"timestamp_format,omitempty"` Timezone string `toml:"timezone,omitempty" json:"timezone,omitempty"` UseCLI bool `toml:"use_cli,omitempty" json:"use_cli,omitempty"` } func NewConfig() *Config { return &Config{ DockerIntervalMs: 1000, ShowLogs: true, ColorLogs: false, RawLogs: false, ShowTimestamp: true, TimestampFormat: "%Y-%m-%dT%H:%M:%S.%8f", Timezone: "UTC", UseCLI: false, GUI: true, LogSearchCaseSensitive: true, ShowSelf: false, ShowStdErr: true, DirSave: "", Host: "", } } func LoadConfigPath(path string, cfg *Config) error { data, err := os.ReadFile(path) if err != nil { return err } content := string(data) if strings.HasSuffix(path, ".toml") { _, err = toml.Decode(content, cfg) if err != nil { return err } } else if strings.HasSuffix(path, ".jsonc") || strings.HasSuffix(path, ".json") { err = parseJSONC(content, cfg) if err != nil { return err } } cfg.DirConfig = path return nil } func LoadConfig(path string) (*Config, error) { cfg := NewConfig() err := LoadConfigPath(path, cfg) if err != nil { return nil, err } return cfg, nil } func parseJSONC(content string, cfg *Config) error { var raw map[string]interface{} commentsRemoved := removeJSONCComments(content) err := json.Unmarshal([]byte(commentsRemoved), &raw) if err != nil { return err } if colors, ok := raw["colors"].(map[string]interface{}); ok { cfg.AppColors = parseAppColors(colors) } if keymap, ok := raw["keymap"].(map[string]interface{}); ok { cfg.Keymap = parseKeymap(keymap) } if val, ok := raw["color_logs"].(bool); ok { cfg.ColorLogs = val } if val, ok := raw["docker_interval"].(float64); ok { cfg.DockerIntervalMs = int(val) } if val, ok := raw["gui"].(bool); ok { cfg.GUI = val } if val, ok := raw["host"].(string); ok { cfg.Host = val } if val, ok := raw["log_search_case_sensitive"].(bool); ok { cfg.LogSearchCaseSensitive = val } if val, ok := raw["raw_logs"].(bool); ok { cfg.RawLogs = val } if val, ok := raw["save_dir"].(string); ok { cfg.DirSave = val } if val, ok := raw["show_logs"].(bool); ok { cfg.ShowLogs = val } if val, ok := raw["show_self"].(bool); ok { cfg.ShowSelf = val } if val, ok := raw["show_std_err"].(bool); ok { cfg.ShowStdErr = val } if val, ok := raw["show_timestamp"].(bool); ok { cfg.ShowTimestamp = val } if val, ok := raw["timestamp_format"].(string); ok { cfg.TimestampFormat = val } if val, ok := raw["timezone"].(string); ok { cfg.Timezone = val } if val, ok := raw["use_cli"].(bool); ok { cfg.UseCLI = val } return nil } func removeJSONCComments(content string) string { lines := strings.Split(content, "\n") var result []string for _, line := range lines { trimmed := strings.TrimSpace(line) if strings.HasPrefix(trimmed, "//") || strings.HasPrefix(trimmed, "/*") || strings.HasPrefix(trimmed, "*") || strings.HasPrefix(trimmed, "*/") { continue } if idx := strings.Index(trimmed, "//"); idx >= 0 { result = append(result, strings.TrimSpace(line[:idx])) } else { result = append(result, line) } } return strings.Join(result, "\n") } func parseAppColors(colors map[string]interface{}) AppColors { acc := AppColors{} if borders, ok := colors["borders"].(map[string]interface{}); ok { acc.Borders = Borders{ Selected: toString(borders["selected"]), Unselected: toString(borders["unselected"]), } } if chartCPU, ok := colors["chart_cpu"].(map[string]interface{}); ok { acc.ChartCPU = ChartCPU{ Background: toString(chartCPU["background"]), Border: toString(chartCPU["border"]), Title: toString(chartCPU["title"]), Max: toString(chartCPU["max"]), Points: toString(chartCPU["points"]), YAxis: toString(chartCPU["y_axis"]), } } if chartMemory, ok := colors["chart_memory"].(map[string]interface{}); ok { acc.ChartMemory = ChartMemory{ Background: toString(chartMemory["background"]), Border: toString(chartMemory["border"]), Title: toString(chartMemory["title"]), Max: toString(chartMemory["max"]), Points: toString(chartMemory["points"]), YAxis: toString(chartMemory["y_axis"]), } } if chartBandwidth, ok := colors["chart_bandwidth"].(map[string]interface{}); ok { acc.ChartBandwidth = ChartBandwidth{ Background: toString(chartBandwidth["background"]), Border: toString(chartBandwidth["border"]), MaxRX: toString(chartBandwidth["max_rx"]), MaxTX: toString(chartBandwidth["max_tx"]), PointsRX: toString(chartBandwidth["points_rx"]), PointsTX: toString(chartBandwidth["points_tx"]), TitleRX: toString(chartBandwidth["title_rx"]), TitleTX: toString(chartBandwidth["title_tx"]), YAxis: toString(chartBandwidth["y_axis"]), } } if chartPorts, ok := colors["chart_ports"].(map[string]interface{}); ok { acc.ChartPorts = ChartPorts{ Background: toString(chartPorts["background"]), Border: toString(chartPorts["border"]), Title: toString(chartPorts["title"]), Headings: toString(chartPorts["headings"]), Text: toString(chartPorts["text"]), } } if commands, ok := colors["commands"].(map[string]interface{}); ok { acc.Commands = Commands{ Background: toString(commands["background"]), Pause: toString(commands["pause"]), Restart: toString(commands["restart"]), Stop: toString(commands["stop"]), Delete: toString(commands["delete"]), Resume: toString(commands["resume"]), Start: toString(commands["start"]), } } if containerState, ok := colors["container_state"].(map[string]interface{}); ok { acc.ContainerState = ContainerState{ Dead: toString(containerState["dead"]), Exited: toString(containerState["exited"]), Paused: toString(containerState["paused"]), Removing: toString(containerState["removing"]), Restarting: toString(containerState["restarting"]), RunningHealthy: toString(containerState["running_healthy"]), RunningUnhealthy: toString(containerState["running_unhealthy"]), Unknown: toString(containerState["unknown"]), } } if containers, ok := colors["containers"].(map[string]interface{}); ok { acc.Containers = Containers{ Background: toString(containers["background"]), Icon: toString(containers["icon"]), Text: toString(containers["text"]), TextRX: toString(containers["text_rx"]), TextTX: toString(containers["text_tx"]), } } if logSearch, ok := colors["log_search"].(map[string]interface{}); ok { acc.LogSearch = LogSearch{ Background: toString(logSearch["background"]), Text: toString(logSearch["text"]), ButtonText: toString(logSearch["button_text"]), Highlight: toString(logSearch["highlight"]), } } if filter, ok := colors["filter"].(map[string]interface{}); ok { acc.Filter = Filter{ Background: toString(filter["background"]), Text: toString(filter["text"]), SelectedFilterBackground: toString(filter["selected_filter_background"]), SelectedFilterText: toString(filter["selected_filter_text"]), Highlight: toString(filter["highlight"]), } } if headersBar, ok := colors["headers_bar"].(map[string]interface{}); ok { acc.HeadersBar = HeadersBar{ Background: toString(headersBar["background"]), LoadingSpinner: toString(headersBar["loading_spinner"]), Text: toString(headersBar["text"]), TextSelected: toString(headersBar["text_selected"]), } } if logs, ok := colors["logs"].(map[string]interface{}); ok { acc.Logs = Logs{ Background: toString(logs["background"]), Text: toString(logs["text"]), } } if popupDelete, ok := colors["popup_delete"].(map[string]interface{}); ok { acc.PopupDelete = PopupDelete{ Background: toString(popupDelete["background"]), Text: toString(popupDelete["text"]), TextHighlight: toString(popupDelete["text_highlight"]), } } if popupError, ok := colors["popup_error"].(map[string]interface{}); ok { acc.PopupError = PopupError{ Background: toString(popupError["background"]), Text: toString(popupError["text"]), } } if popupHelp, ok := colors["popup_help"].(map[string]interface{}); ok { acc.PopupHelp = PopupHelp{ Background: toString(popupHelp["background"]), Text: toString(popupHelp["text"]), TextHighlight: toString(popupHelp["text_highlight"]), } } if popupInfo, ok := colors["popup_info"].(map[string]interface{}); ok { acc.PopupInfo = PopupInfo{ Background: toString(popupInfo["background"]), Text: toString(popupInfo["text"]), } } return acc } func parseKeymap(keymap map[string]interface{}) Keymap { k := Keymap{} applyKeymapEntry(&k.Clear, keymap, "clear") applyKeymapEntry(&k.DeleteConfirm, keymap, "delete_confirm") applyKeymapEntry(&k.DeleteDeny, keymap, "delete_deny") applyKeymapEntry(&k.Exec, keymap, "exec") applyKeymapEntry(&k.FilterMode, keymap, "filter_mode") applyKeymapEntry(&k.Inspect, keymap, "inspect") applyKeymapEntry(&k.ForceRedraw, keymap, "force_redraw") applyKeymapEntry(&k.ScrollBack, keymap, "scroll_back") applyKeymapEntry(&k.ScrollForward, keymap, "scroll_forward") applyKeymapEntry(&k.LogSearchMode, keymap, "log_search_mode") applyKeymapEntry(&k.LogSectionHeightDecrease, keymap, "log_section_height_decrease") applyKeymapEntry(&k.LogSectionHeightIncrease, keymap, "log_section_height_increase") applyKeymapEntry(&k.LogSectionToggle, keymap, "log_section_toggle") applyKeymapEntry(&k.Quit, keymap, "quit") applyKeymapEntry(&k.SaveLogs, keymap, "save_logs") applyKeymapEntry(&k.ScrollDown, keymap, "scroll_down") applyKeymapEntry(&k.ScrollEnd, keymap, "scroll_end") applyKeymapEntry(&k.ScrollStart, keymap, "scroll_start") applyKeymapEntry(&k.ScrollUp, keymap, "scroll_up") applyKeymapEntry(&k.SelectNextPanel, keymap, "select_next_panel") applyKeymapEntry(&k.SelectPreviousPanel, keymap, "select_previous_panel") applyKeymapEntry(&k.SortByName, keymap, "sort_by_name") applyKeymapEntry(&k.SortByState, keymap, "sort_by_state") applyKeymapEntry(&k.SortByStatus, keymap, "sort_by_status") applyKeymapEntry(&k.SortByCPU, keymap, "sort_by_cpu") applyKeymapEntry(&k.SortByMemory, keymap, "sort_by_memory") applyKeymapEntry(&k.SortByID, keymap, "sort_by_id") applyKeymapEntry(&k.SortByImage, keymap, "sort_by_image") applyKeymapEntry(&k.SortByRX, keymap, "sort_by_rx") applyKeymapEntry(&k.SortByTX, keymap, "sort_by_tx") applyKeymapEntry(&k.SortReset, keymap, "sort_reset") applyKeymapEntry(&k.ToggleHelp, keymap, "toggle_help") applyKeymapEntry(&k.ToggleMouseCapture, keymap, "toggle_mouse_capture") if scrollMany, ok := keymap["scroll_many"].(string); ok { k.ScrollMany = scrollMany } return k } func applyKeymapEntry(target *[]string, keymap map[string]interface{}, key string) { if val, ok := keymap[key].([]interface{}); ok { for _, v := range val { if s, ok := v.(string); ok { *target = append(*target, s) } } } } func toString(val interface{}) string { if s, ok := val.(string); ok { return s } return "" } func (c *Config) SaveConfig() error { path := c.DirConfig if path == "" { var err error path, err = GetDefaultConfigPath() if err != nil { return err } } file, err := os.Create(path) if err != nil { return err } defer file.Close() if strings.HasSuffix(path, ".toml") { return toml.NewEncoder(file).Encode(c) } else if strings.HasSuffix(path, ".jsonc") || strings.HasSuffix(path, ".json") { data, err := json.MarshalIndent(c, "", " ") if err != nil { return err } _, err = file.Write(data) return err } return nil } func GetDefaultConfigPath() (string, error) { home, err := os.UserHomeDir() if err != nil { return "", err } configDir := filepath.Join(home, ".config", "oxker") return filepath.Join(configDir, "config.toml"), nil } func (c *Config) MergeCLIArgs(cliArgs *CLIArgs) { if cliArgs == nil { return } if cliArgs.DockerIntervalMs > 0 { c.DockerIntervalMs = cliArgs.DockerIntervalMs } c.RawLogs = cliArgs.RawLogs c.ColorLogs = cliArgs.ColorLogs c.ShowTimestamp = !cliArgs.ShowTimestamp c.UseCLI = cliArgs.UseCLI c.GUI = !cliArgs.DebugMode if cliArgs.Host != "" { c.Host = cliArgs.Host } c.DirConfig = cliArgs.ConfigFile } type CLIArgs struct { DockerIntervalMs int RawLogs bool ColorLogs bool ShowTimestamp bool ShowSelf bool DebugMode bool ConfigFile string Host string NoStdErr bool SaveDir string Timezone string UseCLI bool }