package webui
import (
"bytes"
"fmt"
"html"
"strings"
)
// HTML 渲染辅助函数
// renderTablesHTML 渲染表列表 HTML
func renderTablesHTML(tables []TableListItem) string {
var buf bytes.Buffer
for _, table := range tables {
buf.WriteString(`
`)
buf.WriteString(``)
// Schema 字段列表(默认隐藏)
if len(table.Fields) > 0 {
buf.WriteString(`
`)
for _, field := range table.Fields {
buf.WriteString(`
`)
buf.WriteString(``)
buf.WriteString(html.EscapeString(field.Name))
buf.WriteString(``)
buf.WriteString(``)
buf.WriteString(html.EscapeString(field.Type))
buf.WriteString(``)
if field.Indexed {
buf.WriteString(`●indexed`)
}
buf.WriteString(`
`)
}
buf.WriteString(`
`)
}
buf.WriteString(`
`)
}
return buf.String()
}
// renderDataViewHTML 渲染数据视图 HTML
func renderDataViewHTML(tableName string, schema SchemaInfo, tableData TableDataResponse) string {
var buf bytes.Buffer
// 标题
buf.WriteString(``)
// Active Levels
buf.WriteString(`
`)
buf.WriteString(`
Active Levels
`)
buf.WriteString(`
`)
buf.WriteString(fmt.Sprintf("%d", totalLevels))
buf.WriteString(`
`)
// Total Files
buf.WriteString(`
`)
buf.WriteString(`
Total Files
`)
buf.WriteString(`
`)
buf.WriteString(fmt.Sprintf("%d", totalFiles))
buf.WriteString(`
`)
// Total Size
buf.WriteString(`
`)
buf.WriteString(`
Total Size
`)
buf.WriteString(`
`)
buf.WriteString(formatBytes(totalSize))
buf.WriteString(`
`)
// Next File Number
buf.WriteString(`
`)
buf.WriteString(`
Next File Number
`)
buf.WriteString(`
`)
buf.WriteString(fmt.Sprintf("%d", manifest.NextFileNumber))
buf.WriteString(`
`)
// Last Sequence
buf.WriteString(`
`)
buf.WriteString(`
Last Sequence
`)
buf.WriteString(`
`)
buf.WriteString(fmt.Sprintf("%d", manifest.LastSequence))
buf.WriteString(`
`)
// Total Compactions
buf.WriteString(`
`)
buf.WriteString(`
Total Compactions
`)
buf.WriteString(`
`)
totalCompactions := 0
if manifest.CompactionStats != nil {
if tc, ok := manifest.CompactionStats["total_compactions"]; ok {
if tcInt, ok := tc.(float64); ok {
totalCompactions = int(tcInt)
}
}
}
buf.WriteString(fmt.Sprintf("%d", totalCompactions))
buf.WriteString(`
`)
buf.WriteString(`
`)
// 渲染所有层级(L0-L6)
for i := 0; i <= 6; i++ {
var level *LevelInfo
for j := range manifest.Levels {
if manifest.Levels[j].Level == i {
level = &manifest.Levels[j]
break
}
}
if level == nil {
// 创建空层级
level = &LevelInfo{
Level: i,
FileCount: 0,
TotalSize: 0,
Score: 0,
Files: []FileInfo{},
}
}
buf.WriteString(renderLevelCard(*level))
}
return buf.String()
}
// renderLevelCard 渲染层级卡片
func renderLevelCard(level LevelInfo) string {
var buf bytes.Buffer
scoreClass := "normal"
if level.Score >= 1.0 {
scoreClass = "critical"
} else if level.Score >= 0.8 {
scoreClass = "warning"
}
buf.WriteString(``)
buf.WriteString(``)
// 文件列表(默认隐藏)
buf.WriteString(`
`)
if len(level.Files) == 0 {
buf.WriteString(`
No files in this level
`)
} else {
for _, file := range level.Files {
buf.WriteString(`
`)
buf.WriteString(``)
buf.WriteString(`
`)
buf.WriteString(`Key Range:`)
buf.WriteString(``)
buf.WriteString(fmt.Sprintf("%d - %d", file.MinKey, file.MaxKey))
buf.WriteString(`
`)
buf.WriteString(`
`)
buf.WriteString(`Rows:`)
buf.WriteString(``)
buf.WriteString(formatCount(file.RowCount))
buf.WriteString(`
`)
buf.WriteString(`
`)
}
}
buf.WriteString(`
`)
buf.WriteString(`
`)
return buf.String()
}
// renderPagination 渲染分页 HTML
func renderPagination(data TableDataResponse) string {
var buf bytes.Buffer
buf.WriteString(``)
return buf.String()
}
// formatBytes 格式化字节数
func formatBytes(bytes int64) string {
if bytes == 0 {
return "0 B"
}
const k = 1024
sizes := []string{"B", "KB", "MB", "GB", "TB"}
i := 0
size := float64(bytes)
for size >= k && i < len(sizes)-1 {
size /= k
i++
}
return fmt.Sprintf("%.2f %s", size, sizes[i])
}
// formatCount 格式化数量(K/M)
func formatCount(count int64) string {
if count >= 1000000 {
return fmt.Sprintf("%.1fM", float64(count)/1000000)
}
if count >= 1000 {
return fmt.Sprintf("%.1fK", float64(count)/1000)
}
return fmt.Sprintf("%d", count)
}
// escapeJSString 转义 JavaScript 字符串
func escapeJSString(s string) string {
s = strings.ReplaceAll(s, `\`, `\\`)
s = strings.ReplaceAll(s, `'`, `\'`)
s = strings.ReplaceAll(s, `"`, `\"`)
s = strings.ReplaceAll(s, "\n", `\n`)
s = strings.ReplaceAll(s, "\r", `\r`)
s = strings.ReplaceAll(s, "\t", `\t`)
return s
}
// 数据结构定义
type TableListItem struct {
Name string `json:"name"`
CreatedAt int64 `json:"created_at"`
Fields []FieldInfo `json:"fields"`
}
type FieldInfo struct {
Name string `json:"name"`
Type string `json:"type"`
Indexed bool `json:"indexed"`
Comment string `json:"comment"`
}
type SchemaInfo struct {
Name string `json:"name"`
Fields []FieldInfo `json:"fields"`
}
type TableDataResponse struct {
Data []map[string]any `json:"data"`
Page int64 `json:"page"`
PageSize int64 `json:"pageSize"`
TotalRows int64 `json:"totalRows"`
TotalPages int64 `json:"totalPages"`
}
type ManifestResponse struct {
Levels []LevelInfo `json:"levels"`
NextFileNumber int64 `json:"next_file_number"`
LastSequence int64 `json:"last_sequence"`
CompactionStats map[string]any `json:"compaction_stats"`
}
type LevelInfo struct {
Level int `json:"level"`
FileCount int `json:"file_count"`
TotalSize int64 `json:"total_size"`
Score float64 `json:"score"`
Files []FileInfo `json:"files"`
}
type FileInfo struct {
FileNumber int64 `json:"file_number"`
Level int `json:"level"`
FileSize int64 `json:"file_size"`
MinKey int64 `json:"min_key"`
MaxKey int64 `json:"max_key"`
RowCount int64 `json:"row_count"`
}