T4.4: Extract history_view.go
Move newHistoryView, historyCellText, logFileName, newEvent, and collectActivity out of mainwindow.go into src/ui/history_view.go. Drop the now-unused "sort" import from mainwindow.go. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+1
-1
@@ -271,7 +271,7 @@ Track progress here. Mark tasks complete as they land and pass review.
|
|||||||
- [x] T4.1 — Rename `gui` → `ui`; split app.go into run.go + mainwindow.go _(required Fyne v2.5.3→v2.6.3 upgrade for `fyne.Do`)_
|
- [x] T4.1 — Rename `gui` → `ui`; split app.go into run.go + mainwindow.go _(required Fyne v2.5.3→v2.6.3 upgrade for `fyne.Do`)_
|
||||||
- [x] T4.2 — Extract `jobs_view.go`
|
- [x] T4.2 — Extract `jobs_view.go`
|
||||||
- [x] T4.3 — Extract `job_dialog.go`
|
- [x] T4.3 — Extract `job_dialog.go`
|
||||||
- [ ] T4.4 — Extract `history_view.go`
|
- [x] T4.4 — Extract `history_view.go`
|
||||||
- [ ] T4.5 — Extract `settings_view.go`
|
- [ ] T4.5 — Extract `settings_view.go`
|
||||||
- [ ] T4.6 — Extract `tray.go`, `singleinstance.go`, `layout.go`
|
- [ ] T4.6 — Extract `tray.go`, `singleinstance.go`, `layout.go`
|
||||||
- [ ] T4.7 — Confirm app.go is gone; smoke test both platforms
|
- [ ] T4.7 — Confirm app.go is gone; smoke test both platforms
|
||||||
|
|||||||
@@ -0,0 +1,151 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gitea.mixdep.ru/mix/gosentry/src/domain"
|
||||||
|
|
||||||
|
"fyne.io/fyne/v2"
|
||||||
|
"fyne.io/fyne/v2/container"
|
||||||
|
"fyne.io/fyne/v2/widget"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newEvent(jobID int, jobName string, state string, detail string) event {
|
||||||
|
// Use the same timestamp shape as command run records so the History tab is
|
||||||
|
// visually consistent across startup, UI actions, manual runs, and schedules.
|
||||||
|
return event{
|
||||||
|
Time: time.Now().Format("2006-01-02 15:04:05"),
|
||||||
|
JobID: jobID,
|
||||||
|
JobName: jobName,
|
||||||
|
Trigger: "UI",
|
||||||
|
State: state,
|
||||||
|
Detail: detail,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func collectActivity(jobs []job, runtimes map[int]*domain.JobRuntime) []event {
|
||||||
|
var events []event
|
||||||
|
for _, current := range jobs {
|
||||||
|
// At startup this is usually empty because jobs.yaml does not persist
|
||||||
|
// runtime logs. The function still centralizes the merge for future
|
||||||
|
// history loading from log metadata.
|
||||||
|
if rt := runtimes[current.ID]; rt != nil {
|
||||||
|
events = append(events, rt.Logs...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sort.SliceStable(events, func(left int, right int) bool {
|
||||||
|
return events[left].Time < events[right].Time
|
||||||
|
})
|
||||||
|
return events
|
||||||
|
}
|
||||||
|
|
||||||
|
func newHistoryView(events *[]event) *fyne.Container {
|
||||||
|
descending := false
|
||||||
|
headerText := func(id widget.TableCellID) string {
|
||||||
|
headers := []string{"Time", "Trigger", "Job", "State", "Detail", "Log"}
|
||||||
|
if id.Row < 0 && id.Col == 0 {
|
||||||
|
if descending {
|
||||||
|
return "Time desc"
|
||||||
|
}
|
||||||
|
return "Time asc"
|
||||||
|
}
|
||||||
|
if id.Row < 0 && id.Col >= 0 && id.Col < len(headers) {
|
||||||
|
return headers[id.Col]
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
sortedEvents := func() []event {
|
||||||
|
result := append([]event(nil), (*events)...)
|
||||||
|
sort.SliceStable(result, func(left int, right int) bool {
|
||||||
|
if descending {
|
||||||
|
return result[left].Time > result[right].Time
|
||||||
|
}
|
||||||
|
return result[left].Time < result[right].Time
|
||||||
|
})
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
table := widget.NewTable(
|
||||||
|
func() (int, int) {
|
||||||
|
return len(*events), 6
|
||||||
|
},
|
||||||
|
func() fyne.CanvasObject {
|
||||||
|
label := widget.NewLabel("")
|
||||||
|
label.Wrapping = fyne.TextTruncate
|
||||||
|
return label
|
||||||
|
},
|
||||||
|
func(id widget.TableCellID, item fyne.CanvasObject) {
|
||||||
|
label := item.(*widget.Label)
|
||||||
|
label.SetText(historyCellText(id, sortedEvents()))
|
||||||
|
label.TextStyle = fyne.TextStyle{}
|
||||||
|
label.Refresh()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
table.ShowHeaderRow = true
|
||||||
|
table.CreateHeader = func() fyne.CanvasObject {
|
||||||
|
label := widget.NewLabel("")
|
||||||
|
label.Wrapping = fyne.TextTruncate
|
||||||
|
return label
|
||||||
|
}
|
||||||
|
table.UpdateHeader = func(id widget.TableCellID, item fyne.CanvasObject) {
|
||||||
|
label := item.(*widget.Label)
|
||||||
|
label.SetText(headerText(id))
|
||||||
|
label.TextStyle = fyne.TextStyle{Bold: true}
|
||||||
|
label.Refresh()
|
||||||
|
}
|
||||||
|
table.OnSelected = func(id widget.TableCellID) {
|
||||||
|
if id.Row < 0 && id.Col == 0 {
|
||||||
|
descending = !descending
|
||||||
|
table.Refresh()
|
||||||
|
}
|
||||||
|
table.Unselect(id)
|
||||||
|
}
|
||||||
|
table.SetColumnWidth(0, 150)
|
||||||
|
table.SetColumnWidth(1, 90)
|
||||||
|
table.SetColumnWidth(2, 170)
|
||||||
|
table.SetColumnWidth(3, 90)
|
||||||
|
table.SetColumnWidth(4, 260)
|
||||||
|
table.SetColumnWidth(5, 240)
|
||||||
|
return container.NewPadded(table)
|
||||||
|
}
|
||||||
|
|
||||||
|
func historyCellText(id widget.TableCellID, events []event) string {
|
||||||
|
if id.Row < 0 || id.Row >= len(events) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
current := events[id.Row]
|
||||||
|
trigger := current.Trigger
|
||||||
|
if trigger == "" {
|
||||||
|
trigger = "Unknown"
|
||||||
|
}
|
||||||
|
switch id.Col {
|
||||||
|
case 0:
|
||||||
|
return current.Time
|
||||||
|
case 1:
|
||||||
|
return trigger
|
||||||
|
case 2:
|
||||||
|
return current.JobName
|
||||||
|
case 3:
|
||||||
|
return current.State
|
||||||
|
case 4:
|
||||||
|
return current.Detail
|
||||||
|
case 5:
|
||||||
|
return logFileName(current.LogFile)
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func logFileName(path string) string {
|
||||||
|
path = strings.TrimSpace(path)
|
||||||
|
if path == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
path = strings.ReplaceAll(path, "\\", "/")
|
||||||
|
if slash := strings.LastIndex(path, "/"); slash >= 0 {
|
||||||
|
return path[slash+1:]
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"runtime"
|
"runtime"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
"sort"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -137,144 +136,6 @@ func (l minWidthLayout) Layout(objects []fyne.CanvasObject, size fyne.Size) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func newEvent(jobID int, jobName string, state string, detail string) event {
|
|
||||||
// Use the same timestamp shape as command run records so the History tab is
|
|
||||||
// visually consistent across startup, UI actions, manual runs, and schedules.
|
|
||||||
return event{
|
|
||||||
Time: time.Now().Format("2006-01-02 15:04:05"),
|
|
||||||
JobID: jobID,
|
|
||||||
JobName: jobName,
|
|
||||||
Trigger: "UI",
|
|
||||||
State: state,
|
|
||||||
Detail: detail,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func collectActivity(jobs []job, runtimes map[int]*domain.JobRuntime) []event {
|
|
||||||
var events []event
|
|
||||||
for _, current := range jobs {
|
|
||||||
// At startup this is usually empty because jobs.yaml does not persist
|
|
||||||
// runtime logs. The function still centralizes the merge for future
|
|
||||||
// history loading from log metadata.
|
|
||||||
if rt := runtimes[current.ID]; rt != nil {
|
|
||||||
events = append(events, rt.Logs...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sort.SliceStable(events, func(left int, right int) bool {
|
|
||||||
return events[left].Time < events[right].Time
|
|
||||||
})
|
|
||||||
return events
|
|
||||||
}
|
|
||||||
|
|
||||||
func newHistoryView(events *[]event) *fyne.Container {
|
|
||||||
descending := false
|
|
||||||
headerText := func(id widget.TableCellID) string {
|
|
||||||
headers := []string{"Time", "Trigger", "Job", "State", "Detail", "Log"}
|
|
||||||
if id.Row < 0 && id.Col == 0 {
|
|
||||||
if descending {
|
|
||||||
return "Time desc"
|
|
||||||
}
|
|
||||||
return "Time asc"
|
|
||||||
}
|
|
||||||
if id.Row < 0 && id.Col >= 0 && id.Col < len(headers) {
|
|
||||||
return headers[id.Col]
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
sortedEvents := func() []event {
|
|
||||||
result := append([]event(nil), (*events)...)
|
|
||||||
sort.SliceStable(result, func(left int, right int) bool {
|
|
||||||
if descending {
|
|
||||||
return result[left].Time > result[right].Time
|
|
||||||
}
|
|
||||||
return result[left].Time < result[right].Time
|
|
||||||
})
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
table := widget.NewTable(
|
|
||||||
func() (int, int) {
|
|
||||||
return len(*events), 6
|
|
||||||
},
|
|
||||||
func() fyne.CanvasObject {
|
|
||||||
label := widget.NewLabel("")
|
|
||||||
label.Wrapping = fyne.TextTruncate
|
|
||||||
return label
|
|
||||||
},
|
|
||||||
func(id widget.TableCellID, item fyne.CanvasObject) {
|
|
||||||
label := item.(*widget.Label)
|
|
||||||
label.SetText(historyCellText(id, sortedEvents()))
|
|
||||||
label.TextStyle = fyne.TextStyle{}
|
|
||||||
label.Refresh()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
table.ShowHeaderRow = true
|
|
||||||
table.CreateHeader = func() fyne.CanvasObject {
|
|
||||||
label := widget.NewLabel("")
|
|
||||||
label.Wrapping = fyne.TextTruncate
|
|
||||||
return label
|
|
||||||
}
|
|
||||||
table.UpdateHeader = func(id widget.TableCellID, item fyne.CanvasObject) {
|
|
||||||
label := item.(*widget.Label)
|
|
||||||
label.SetText(headerText(id))
|
|
||||||
label.TextStyle = fyne.TextStyle{Bold: true}
|
|
||||||
label.Refresh()
|
|
||||||
}
|
|
||||||
table.OnSelected = func(id widget.TableCellID) {
|
|
||||||
if id.Row < 0 && id.Col == 0 {
|
|
||||||
descending = !descending
|
|
||||||
table.Refresh()
|
|
||||||
}
|
|
||||||
table.Unselect(id)
|
|
||||||
}
|
|
||||||
table.SetColumnWidth(0, 150)
|
|
||||||
table.SetColumnWidth(1, 90)
|
|
||||||
table.SetColumnWidth(2, 170)
|
|
||||||
table.SetColumnWidth(3, 90)
|
|
||||||
table.SetColumnWidth(4, 260)
|
|
||||||
table.SetColumnWidth(5, 240)
|
|
||||||
return container.NewPadded(table)
|
|
||||||
}
|
|
||||||
|
|
||||||
func historyCellText(id widget.TableCellID, events []event) string {
|
|
||||||
if id.Row < 0 || id.Row >= len(events) {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
current := events[id.Row]
|
|
||||||
trigger := current.Trigger
|
|
||||||
if trigger == "" {
|
|
||||||
trigger = "Unknown"
|
|
||||||
}
|
|
||||||
switch id.Col {
|
|
||||||
case 0:
|
|
||||||
return current.Time
|
|
||||||
case 1:
|
|
||||||
return trigger
|
|
||||||
case 2:
|
|
||||||
return current.JobName
|
|
||||||
case 3:
|
|
||||||
return current.State
|
|
||||||
case 4:
|
|
||||||
return current.Detail
|
|
||||||
case 5:
|
|
||||||
return logFileName(current.LogFile)
|
|
||||||
default:
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func logFileName(path string) string {
|
|
||||||
path = strings.TrimSpace(path)
|
|
||||||
if path == "" {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
path = strings.ReplaceAll(path, "\\", "/")
|
|
||||||
if slash := strings.LastIndex(path, "/"); slash >= 0 {
|
|
||||||
return path[slash+1:]
|
|
||||||
}
|
|
||||||
return path
|
|
||||||
}
|
|
||||||
|
|
||||||
func settingsView(w fyne.Window, svc *app.Service) fyne.CanvasObject {
|
func settingsView(w fyne.Window, svc *app.Service) fyne.CanvasObject {
|
||||||
store := svc.Store()
|
store := svc.Store()
|
||||||
startOnLogin := widget.NewCheck("Start on login", nil)
|
startOnLogin := widget.NewCheck("Start on login", nil)
|
||||||
|
|||||||
Reference in New Issue
Block a user