diff --git a/internal/cli/response.go b/internal/cli/response.go index 17eb7edc69..c4adb3591d 100644 --- a/internal/cli/response.go +++ b/internal/cli/response.go @@ -128,18 +128,11 @@ func (r *CommonDataResponse) SetOutputFormat(format OutputFormat) { func (r *CommonDataResponse) orderedMetricTable() []map[string]interface{} { table := make([]map[string]interface{}, 0) - if orderRaw, ok := r.Data["_order"]; ok { - if orderSlice, ok := orderRaw.([]interface{}); ok { - for _, keyRaw := range orderSlice { - key := fmt.Sprintf("%v", keyRaw) - if value, exists := r.Data[key]; exists { - table = append(table, map[string]interface{}{ - "Metric": key, - "Value": value, - }) - } - } - } + for key, value := range r.Data { + table = append(table, map[string]interface{}{ + "Metric": key, + "Value": value, + }) } return table } @@ -1015,22 +1008,13 @@ func (r *UserIndexResponse) PrintOut() { } summaryTable := r.orderedMetricTable() - indexColumns := []string{"index", "health", "status", "docs.count", "dataset.size", "store.size"} indexTable := make([]map[string]interface{}, 0) indicesRaw, hasIndices := r.Data["indices"] if hasIndices { if indices, ok := indicesRaw.([]interface{}); ok { for _, idx := range indices { if m, ok := idx.(map[string]interface{}); ok { - orderedRow := make(map[string]interface{}) - for _, col := range indexColumns { - if v, exists := m[col]; exists { - orderedRow[col] = v - } else { - orderedRow[col] = "" - } - } - indexTable = append(indexTable, orderedRow) + indexTable = append(indexTable, m) } } } @@ -1060,7 +1044,7 @@ func (r *UserIndexResponse) PrintOut() { if len(indexTable) > 0 { fmt.Println() fmt.Println("Index Details:") - PrintTableSimpleByFormatWithOrder(indexTable, indexColumns, r.OutputFormat) + PrintTableSimpleByFormat(indexTable, r.OutputFormat) } else if hasIndices { fmt.Println() fmt.Println("No indices found for this user.") @@ -1083,22 +1067,13 @@ func (r *UserStorageResponse) PrintOut() { } summaryTable := r.orderedMetricTable() - fileColumns := []string{"name", "size"} fileTable := make([]map[string]interface{}, 0) filesRaw, hasFiles := r.Data["files"] if hasFiles { if files, ok := filesRaw.([]interface{}); ok { for _, f := range files { if m, ok := f.(map[string]interface{}); ok { - orderedRow := make(map[string]interface{}) - for _, col := range fileColumns { - if v, exists := m[col]; exists { - orderedRow[col] = v - } else { - orderedRow[col] = "" - } - } - fileTable = append(fileTable, orderedRow) + fileTable = append(fileTable, m) } } } @@ -1128,7 +1103,7 @@ func (r *UserStorageResponse) PrintOut() { if len(fileTable) > 0 { fmt.Println() fmt.Println("Files(Top 10):") - PrintTableSimpleByFormatWithOrder(fileTable, fileColumns, r.OutputFormat) + PrintTableSimpleByFormat(fileTable, r.OutputFormat) } else if hasFiles { fmt.Println() fmt.Println("No files found for this user.") @@ -1170,7 +1145,7 @@ func (r *UserQuotaResponse) PrintOut() { } } if len(summaryTable) > 0 { - PrintTableSimpleByFormatWithOrder(summaryTable, []string{"Metric", "Used", "Limit"}, r.OutputFormat) + PrintTableSimpleByFormat(summaryTable, r.OutputFormat) } } @@ -1180,11 +1155,7 @@ type OrderedCommonResponse struct { func (r *OrderedCommonResponse) PrintOut() { if r.Code == 0 { - if colNames, cleanData, ok := ExtractColumnsAndCleanData(r.Data); ok { - PrintTableSimpleByFormatWithOrder(cleanData, colNames, r.OutputFormat) - } else { - PrintTableSimpleByFormat(r.Data, r.OutputFormat) - } + PrintTableSimpleByFormat(r.Data, r.OutputFormat) } else { fmt.Println("ERROR") fmt.Printf("%d, %s\n", r.Code, r.Message) @@ -1197,17 +1168,8 @@ type OrderedCommonDataResponse struct { func (r *OrderedCommonDataResponse) PrintOut() { if r.Code == 0 { - if table := r.orderedMetricTable(); len(table) > 0 { - PrintTableSimpleByFormat(table, r.OutputFormat) - } else { - table := make([]map[string]interface{}, 0) - for key, value := range r.Data { - elem := map[string]interface{}{ - "field": key, - "value": value, - } - table = append(table, elem) - } + table := r.orderedMetricTable() + if len(table) > 0 { PrintTableSimpleByFormat(table, r.OutputFormat) } } else { @@ -1239,23 +1201,14 @@ func (r *QuotaSummaryResponse) PrintOut() { return } - sections := []struct { - key string - title string - columns []string - }{ - {"storage", "Storage", []string{"Plan", "Users", "Avg Used", "Limit", "Avg Usage"}}, - {"apps", "Apps", []string{"Plan", "Avg Used", "Limit", "Avg Usage"}}, - {"api", "API Requests", []string{"Plan", "Tokens", "Limit/min"}}, - } + sections := []string{"storage", "apps", "api"} - for i, section := range sections { + for i, key := range sections { if i > 0 { fmt.Println() } - fmt.Printf("--- %s ---\n", section.title) - rowsRaw, ok := r.Data[section.key] + rowsRaw, ok := r.Data[key] if !ok { fmt.Println("No data") continue @@ -1270,19 +1223,11 @@ func (r *QuotaSummaryResponse) PrintOut() { table := make([]map[string]interface{}, 0, len(rows)) for _, row := range rows { if m, ok := row.(map[string]interface{}); ok { - orderedRow := make(map[string]interface{}) - for _, col := range section.columns { - if v, exists := m[col]; exists { - orderedRow[col] = v - } else { - orderedRow[col] = "" - } - } - table = append(table, orderedRow) + table = append(table, m) } } - PrintTableSimpleByFormatWithOrder(table, section.columns, r.OutputFormat) + PrintTableSimpleByFormat(table, r.OutputFormat) } } diff --git a/internal/cli/table.go b/internal/cli/table.go index cafb955f0d..26130634c3 100644 --- a/internal/cli/table.go +++ b/internal/cli/table.go @@ -197,118 +197,6 @@ func PrintTableSimpleByFormat(data []map[string]interface{}, format OutputFormat } } -// PrintTableSimpleByFormatWithOrder prints data with columns in the specified order -func PrintTableSimpleByFormatWithOrder(data []map[string]interface{}, columns []string, format OutputFormat) { - if len(data) == 0 { - if format == OutputFormatJSON { - fmt.Println("[]") - } else if format == OutputFormatPlain { - fmt.Println("(empty)") - } else { - fmt.Println("No data to print") - } - return - } - - if format == OutputFormatJSON { - jsonData, err := json.MarshalIndent(data, "", " ") - if err != nil { - fmt.Printf("Error marshaling JSON: %v\n", err) - return - } - fmt.Println(string(jsonData)) - return - } - - colIsNumeric := make(map[string]bool) - for _, col := range columns { - isNumeric := true - for _, item := range data { - if val, ok := item[col]; ok { - if !isNumericValue(val) { - isNumeric = false - break - } - } - } - colIsNumeric[col] = isNumeric - } - - colWidths := make(map[string]int) - for _, col := range columns { - maxWidth := getStringWidth(strings.ToLower(col)) - for _, item := range data { - value := formatValue(item[col]) - valueWidth := getStringWidth(value) - if valueWidth > maxWidth { - maxWidth = valueWidth - } - } - if maxWidth > maxColWidth { - maxWidth = maxColWidth - } - if maxWidth < 2 { - maxWidth = 2 - } - colWidths[col] = maxWidth - } - - if format == OutputFormatPlain { - headerParts := make([]string, 0, len(columns)) - for _, col := range columns { - headerParts = append(headerParts, padCell(strings.ToLower(col), colWidths[col], colIsNumeric[col])) - } - fmt.Println(strings.Join(headerParts, " ")) - - for _, item := range data { - rowParts := make([]string, 0, len(columns)) - for _, col := range columns { - value := formatValue(item[col]) - valueWidth := getStringWidth(value) - if valueWidth > colWidths[col] { - runes := []rune(value) - value = truncateStringByWidth(runes, colWidths[col]) - valueWidth = getStringWidth(value) - } - rowParts = append(rowParts, padCell(value, colWidths[col], colIsNumeric[col])) - } - fmt.Println(strings.Join(rowParts, " ")) - } - } else { - separatorParts := make([]string, 0, len(columns)) - for _, col := range columns { - separatorParts = append(separatorParts, strings.Repeat("-", colWidths[col]+2)) - } - separator := "+" + strings.Join(separatorParts, "+") + "+" - - fmt.Println(separator) - headerParts := make([]string, 0, len(columns)) - for _, col := range columns { - headerParts = append(headerParts, fmt.Sprintf(" %-*s ", colWidths[col], col)) - } - fmt.Println("|" + strings.Join(headerParts, "|") + "|") - fmt.Println(separator) - - for _, item := range data { - rowParts := make([]string, 0, len(columns)) - for _, col := range columns { - value := formatValue(item[col]) - valueWidth := getStringWidth(value) - if valueWidth > colWidths[col] { - runes := []rune(value) - value = truncateStringByWidth(runes, colWidths[col]) - valueWidth = getStringWidth(value) - } - rowParts = append(rowParts, fmt.Sprintf(" %s ", padCell(value, colWidths[col], false))) - - } - fmt.Println("|" + strings.Join(rowParts, "|") + "|") - } - - fmt.Println(separator) - } -} - // formatValue formats a value for display func formatValue(v interface{}) string { if v == nil { @@ -404,32 +292,3 @@ func isHalfWidth(r rune) bool { } return false } - -func ExtractColumnsAndCleanData(data []map[string]interface{}) ([]string, []map[string]interface{}, bool) { - if len(data) == 0 { - return nil, nil, false - } - columnsRaw, ok := data[0]["_columns"] - if !ok { - return nil, nil, false - } - columns, ok := columnsRaw.([]interface{}) - if !ok { - return nil, nil, false - } - colNames := make([]string, 0, len(columns)) - for _, c := range columns { - colNames = append(colNames, fmt.Sprintf("%v", c)) - } - cleanData := make([]map[string]interface{}, 0, len(data)) - for _, row := range data { - cleanRow := make(map[string]interface{}, len(row)) - for k, v := range row { - if k != "_columns" { - cleanRow[k] = v - } - } - cleanData = append(cleanData, cleanRow) - } - return colNames, cleanData, true -} diff --git a/internal/common/format.go b/internal/common/format.go index b2bc02ac91..b8be5970df 100644 --- a/internal/common/format.go +++ b/internal/common/format.go @@ -20,7 +20,9 @@ import ( "encoding/base64" "fmt" "regexp" + "strconv" "strings" + "time" ) // PtrString formats a pointer value as a string for debug/log output. @@ -86,6 +88,79 @@ func DecodeFromBase64(encoded string) (string, error) { return string(decoded), nil } +func FormatBytes(bytes int64) string { + const ( + KB = 1024 + MB = KB * 1024 + GB = MB * 1024 + TB = GB * 1024 + ) + switch { + case bytes >= TB: + return fmt.Sprintf("%.1f TB", float64(bytes)/float64(TB)) + case bytes >= GB: + return fmt.Sprintf("%.1f GB", float64(bytes)/float64(GB)) + case bytes >= MB: + return fmt.Sprintf("%.1f MB", float64(bytes)/float64(MB)) + case bytes >= KB: + return fmt.Sprintf("%.1f KB", float64(bytes)/float64(KB)) + default: + return fmt.Sprintf("%d B", bytes) + } +} + +func FormatNumber(n int64) string { + s := fmt.Sprintf("%d", n) + parts := []string{} + for i := len(s); i > 0; i -= 3 { + start := i - 3 + if start < 0 { + start = 0 + } + parts = append([]string{s[start:i]}, parts...) + } + return strings.Join(parts, ",") +} + +func ParseBytesString(s string) int64 { + s = strings.TrimSpace(strings.ToLower(s)) + if s == "" || s == "-" || s == "0" { + return 0 + } + + var multiplier int64 = 1 + switch { + case strings.HasSuffix(s, "tb"): + multiplier = 1024 * 1024 * 1024 * 1024 + s = strings.TrimSuffix(s, "tb") + case strings.HasSuffix(s, "gb"): + multiplier = 1024 * 1024 * 1024 + s = strings.TrimSuffix(s, "gb") + case strings.HasSuffix(s, "mb"): + multiplier = 1024 * 1024 + s = strings.TrimSuffix(s, "mb") + case strings.HasSuffix(s, "kb"): + multiplier = 1024 + s = strings.TrimSuffix(s, "kb") + case strings.HasSuffix(s, "b"): + s = strings.TrimSuffix(s, "b") + } + + s = strings.TrimSpace(s) + val, err := strconv.ParseFloat(s, 64) + if err != nil { + return 0 + } + return int64(val * float64(multiplier)) +} + +func FormatTime(t *int64) string { + if t == nil { + return "N/A" + } + return time.UnixMilli(*t).Format("2006-01-02 15:04:05") +} + func IsValidString(v interface{}) bool { str, ok := v.(string) return ok && str != "" diff --git a/internal/dao/enterprise_data.go b/internal/dao/enterprise_data.go new file mode 100644 index 0000000000..0e180b9abc --- /dev/null +++ b/internal/dao/enterprise_data.go @@ -0,0 +1,17 @@ +// +// Copyright 2026 The InfiniFlow Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package dao diff --git a/internal/dao/enterprise_tasks.go b/internal/dao/enterprise_tasks.go new file mode 100644 index 0000000000..0e180b9abc --- /dev/null +++ b/internal/dao/enterprise_tasks.go @@ -0,0 +1,17 @@ +// +// Copyright 2026 The InfiniFlow Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package dao diff --git a/internal/dao/enterprise_user.go b/internal/dao/enterprise_user.go new file mode 100644 index 0000000000..0e180b9abc --- /dev/null +++ b/internal/dao/enterprise_user.go @@ -0,0 +1,17 @@ +// +// Copyright 2026 The InfiniFlow Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package dao