Skip to content
113 changes: 90 additions & 23 deletions internal/ui/dataset_info/dataset_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package dataset_info

import (
"fmt"
"strings"
"zfs-file-history/internal/logging"
"zfs-file-history/internal/ui/shortcut_helper"
"zfs-file-history/internal/ui/theme"
Expand All @@ -17,7 +18,7 @@ import (
type DatasetInfoComponent struct {
application *tview.Application
dataset *zfs.Dataset
layout *tview.Table
layout *tview.TextView
}

func NewDatasetInfo(application *tview.Application) *DatasetInfoComponent {
Expand Down Expand Up @@ -58,7 +59,10 @@ type DatasetInfoTableEntry struct {
}

func (datasetInfo *DatasetInfoComponent) createLayout() {
layout := tview.NewTable()
layout := tview.NewTextView().
SetDynamicColors(true).
SetWrap(false).
SetScrollable(true)
layout.SetBorder(true)
uiutil.SetupWindow(layout, "Dataset")

Expand All @@ -82,12 +86,23 @@ func (datasetInfo *DatasetInfoComponent) updateUi() {
properties := []*DatasetInfoTableEntry{
{Name: "Type", Value: dataset.GetType()},
{Name: "Name", Value: dataset.GetName()},
{Name: "Creation", Value: dataset.GetCreationString().Format(theme.Style.Format.DateTime)},
{Name: "Mountpoint", Value: dataset.GetMountPoint()},
{Name: "Mounted", Value: dataset.GetMounted()},
{Name: "Readonly", Value: dataset.GetReadonly()}, // "on" or "off"
{Name: "Volsize", Value: humanize.IBytes(dataset.GetVolSize())},
{Name: "Avail", Value: humanize.IBytes(dataset.GetAvailable())},
{Name: "Used", Value: humanize.IBytes(dataset.GetUsed())},
{Name: "Compression", Value: dataset.GetCompression()},
{Name: "Compression", Value: fmt.Sprintf("%s (%s)", dataset.GetCompression(), dataset.GetCompressRatio())}, // Combine for compact view
{Name: "Snapdir", Value: dataset.GetSnapdir()}, // "visible" or "hidden"
{Name: "Case", Value: dataset.GetCaseSensitivity()}, // "sensitive" / "insensitive"
}

// If encryption is utilized on the host pool
if dataset.IsEncrypted() {
properties = append(properties, &DatasetInfoTableEntry{
Name: "Encryption", Value: fmt.Sprintf("%s [%s]", dataset.GetEncryption(), dataset.GetKeyStatus()),
})
}

if !util.IsBlank(dataset.GetOrigin()) {
Expand All @@ -103,28 +118,35 @@ func (datasetInfo *DatasetInfoComponent) updateUi() {
}

datasetInfo.layout.Clear()
columns, rows := 2, len(properties)
for row := 0; row < rows; row++ {
entry := properties[row]

for col := 0; col < columns; col++ {
var text string
var cellAlignment int
var cellColor = tcell.ColorWhite
if col == 0 {
text = fmt.Sprintf("%s:", entry.Name)
cellAlignment = tview.AlignRight
cellColor = theme.Colors.Layout.Table.Header
} else {
text = entry.Value
cellAlignment = tview.AlignLeft
}
datasetInfo.layout.SetCell(
row, col,
tview.NewTableCell(text).SetAlign(cellAlignment).SetTextColor(cellColor),
)

// Calculate alignment padding dynamically based on longest key name
maxKeyLen := 0
for _, entry := range properties {
if len(entry.Name) > maxKeyLen {
maxKeyLen = len(entry.Name)
}
}

keyColorTag := colorTag(theme.Colors.Layout.Table.Header)
var out strings.Builder

for _, entry := range properties {
valueColor := resolveValueColor(entry.Name, entry.Value)
valueColorTag := colorTag(valueColor)

// Format key with trailing colon, maintaining clean alignment padding
labelText := fmt.Sprintf("%s:", entry.Name)

out.WriteString(fmt.Sprintf("%s%*s[-] %s%s[-]\n",
keyColorTag,
maxKeyLen+1, // +1 maps to the colon addition
tview.Escape(labelText),
valueColorTag,
tview.Escape(entry.Value),
))
}

datasetInfo.layout.SetText(out.String())
}

func (datasetInfo *DatasetInfoComponent) HasFocus() bool {
Expand All @@ -149,3 +171,48 @@ func (datasetInfo *DatasetInfoComponent) CreateSnapshot(name string) error {
func (datasetInfo *DatasetInfoComponent) GetShortcutMap() []shortcut_helper.ShortcutEntry {
return []shortcut_helper.ShortcutEntry{}
}

// Helper formatting utilities matching ConfigInfo Component patterns

func colorTag(color tcell.Color) string {
r, g, b := color.RGB()
return fmt.Sprintf("[#%02x%02x%02x]", uint8(r), uint8(g), uint8(b))
}

func resolveValueColor(name, value string) tcell.Color {
if value == "" || value == "-" || strings.EqualFold(value, "none") {
return tcell.ColorGray
}

lowerName := strings.ToLower(name)
lowerValue := strings.ToLower(value)

// Warning / Restrictive States
if lowerName == "readonly" && lowerValue == "on" {
return tcell.ColorOrange // Visual cue that writes/restores are blocked
}
if strings.Contains(lowerValue, "unavailable") {
return tcell.ColorRed // Key is missing/locked
}

// Paths / Mountpoints
if strings.HasPrefix(value, "/") || lowerName == "mountpoint" || lowerName == "origin" {
return tcell.ColorLightBlue
}

// Booleans / Positive Flags
if lowerValue == "yes" || lowerValue == "true" || lowerValue == "on" || lowerValue == "visible" {
return tcell.ColorGreen
}
if lowerValue == "no" || lowerValue == "false" || lowerValue == "off" || lowerValue == "hidden" {
return tcell.ColorRed
}

// File Sizes & Ratios
if lowerName == "volsize" || lowerName == "avail" || lowerName == "used" || strings.Contains(lowerValue, "x") {
return tcell.ColorYellow
}

// Fallback Default String Color
return tcell.ColorWhite
}
22 changes: 11 additions & 11 deletions internal/ui/file_browser/table_container.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ func createFileBrowserTable(application *tview.Application) *table.RowSelectionT
}

func fileBrowserEntryTableCellsFunction(row int, columns []*table.Column, entry *data.FileBrowserEntry) (cells []*tview.TableCell) {
status := determineStatusIndicator(entry)
statusColor := determineStatusColor(entry)
statusCellText := determineStatusIndicator(entry)
statusCellColor := determineStatusColor(entry)
typeCellText := determineTypeCellText(entry)
typeCellColor := determineTypeCellColor(entry)

Expand All @@ -44,14 +44,14 @@ func fileBrowserEntryTableCellsFunction(row int, columns []*table.Column, entry
if entry.GetStat().IsDir() {
cellText = fmt.Sprintf("/%s", cellText)
}
cellColor = statusColor
cellColor = statusCellColor
case columnType:
cellText = typeCellText
cellColor = typeCellColor
cellAlignment = tview.AlignCenter
case columnDiff:
cellText = status
cellColor = statusColor
cellText = statusCellText
cellColor = statusCellColor
cellAlignment = tview.AlignCenter
case columnPermissions:
cellText = determinePermissionsText(entry)
Expand All @@ -66,10 +66,10 @@ func fileBrowserEntryTableCellsFunction(row int, columns []*table.Column, entry
cellText = entry.GetStat().ModTime().Format(theme.Style.Format.DateTime)
switch entry.DiffState {
case diff_state.Added, diff_state.Deleted:
cellColor = statusColor
cellColor = statusCellColor
case diff_state.Modified:
if entry.RealFile.Stat.ModTime() != entry.SnapshotFiles[0].Stat.ModTime() {
cellColor = statusColor
cellColor = statusCellColor
} else {
cellColor = tcell.ColorWhite
}
Expand All @@ -87,10 +87,10 @@ func fileBrowserEntryTableCellsFunction(row int, columns []*table.Column, entry
}
switch entry.DiffState {
case diff_state.Added, diff_state.Deleted:
cellColor = statusColor
cellColor = statusCellColor
case diff_state.Modified:
if entry.RealFile.Stat.Size() != entry.SnapshotFiles[0].Stat.Size() {
cellColor = statusColor
cellColor = statusCellColor
} else {
cellColor = tcell.ColorWhite
}
Expand All @@ -107,11 +107,11 @@ func fileBrowserEntryTableCellsFunction(row int, columns []*table.Column, entry
SetAlign(cellAlignment).
SetExpansion(cellExpansion)

// Keep row status visible while selected by using statusColor as selected background.
// Keep row statusCellText visible while selected by using statusCellColor as selected background.
cell.SetSelectedStyle(
tcell.StyleDefault.
Foreground(theme.Colors.Layout.Table.SelectedForeground).
Background(statusColor),
Background(statusCellColor),
)
cells = append(cells, cell)
}
Expand Down
42 changes: 40 additions & 2 deletions internal/ui/snapshot_browser/table_container.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,11 @@ func createSnapshotBrowserTable(application *tview.Application) *table.RowSelect

func createSnapshotBrowserTableCells(row int, columns []*table.Column, entry *data.SnapshotBrowserEntry) (cells []*tview.TableCell) {
result := []*tview.TableCell{}
statusColor := determineStatusColor(entry)
for _, column := range columns {
cellText := "N/A"
cellAlign := tview.AlignLeft
cellColor := tcell.ColorWhite
cellColor := determineBaseTextColor(entry)
switch column {
case columnDate:
cellText = entry.Snapshot.Properties.CreationDate.Format(theme.Style.Format.DateTime)
Expand Down Expand Up @@ -65,12 +66,49 @@ func createSnapshotBrowserTableCells(row int, columns []*table.Column, entry *da
cellText = fmt.Sprintf("%d", entry.Snapshot.Properties.Clones)
}
cell := tview.NewTableCell(cellText).
SetTextColor(cellColor).SetAlign(cellAlign)
SetTextColor(cellColor).
SetAlign(cellAlign)

cell.SetSelectedStyle(
tcell.StyleDefault.
Foreground(theme.Colors.Layout.Table.SelectedForeground).
Background(statusColor),
)
result = append(result, cell)
}
return result
}

func determineStatusColor(entry *data.SnapshotBrowserEntry) tcell.Color {
switch entry.DiffState {
case diff_state.Equal:
return theme.Colors.SnapshotBrowser.Table.State.Equal
case diff_state.Deleted:
return theme.Colors.SnapshotBrowser.Table.State.SnapshotOnly
case diff_state.Added:
return theme.Colors.SnapshotBrowser.Table.State.LocalOnly
case diff_state.Modified:
return theme.Colors.SnapshotBrowser.Table.State.Modified
case diff_state.Unknown:
fallthrough
default:
return theme.Colors.SnapshotBrowser.Table.State.Unknown
}
}

func determineBaseTextColor(entry *data.SnapshotBrowserEntry) tcell.Color {
switch entry.DiffState {
case diff_state.Deleted:
fallthrough
case diff_state.Added:
fallthrough
case diff_state.Modified:
return tcell.ColorWhite
default:
return tcell.ColorGray
}
}

func createSnapshotBrowserTableSortFunction(entries []*data.SnapshotBrowserEntry, columnToSortBy *table.Column, inverted bool) []*data.SnapshotBrowserEntry {
sort.SliceStable(entries, func(i, j int) bool {
a := entries[i]
Expand Down
Loading
Loading