more UI and change default local user permissions
This commit is contained in:
48
app.go
48
app.go
@@ -744,6 +744,52 @@ func (a *App) collectCIFSMountConfig(defaults CIFSMountConfig) (CIFSMountConfig,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func defaultCIFSMountConfig() CIFSMountConfig {
|
||||
uid, gid := defaultLocalMountOwnerGroup(os.Getenv, user.Current, user.Lookup, user.LookupGroupId)
|
||||
return CIFSMountConfig{
|
||||
UID: uid,
|
||||
GID: gid,
|
||||
}
|
||||
}
|
||||
|
||||
func defaultLocalMountOwnerGroup(
|
||||
getenv func(string) string,
|
||||
currentUser func() (*user.User, error),
|
||||
lookupUser func(string) (*user.User, error),
|
||||
lookupGroupID func(string) (*user.Group, error),
|
||||
) (uid string, gid string) {
|
||||
resolveGroup := func(groupID string) string {
|
||||
if strings.TrimSpace(groupID) == "" {
|
||||
return ""
|
||||
}
|
||||
group, err := lookupGroupID(groupID)
|
||||
if err != nil {
|
||||
return strings.TrimSpace(groupID)
|
||||
}
|
||||
return strings.TrimSpace(group.Name)
|
||||
}
|
||||
|
||||
if sudoUser := strings.TrimSpace(getenv("SUDO_USER")); sudoUser != "" {
|
||||
uid = sudoUser
|
||||
if sudoGID := strings.TrimSpace(getenv("SUDO_GID")); sudoGID != "" {
|
||||
return uid, resolveGroup(sudoGID)
|
||||
}
|
||||
if u, err := lookupUser(sudoUser); err == nil {
|
||||
return uid, resolveGroup(u.Gid)
|
||||
}
|
||||
return uid, ""
|
||||
}
|
||||
|
||||
u, err := currentUser()
|
||||
if err != nil {
|
||||
return "", ""
|
||||
}
|
||||
|
||||
uid = strings.TrimSpace(u.Username)
|
||||
gid = resolveGroup(u.Gid)
|
||||
return uid, gid
|
||||
}
|
||||
|
||||
func (a *App) ensureMountPoint(path string) error {
|
||||
info, err := os.Stat(path)
|
||||
if err == nil {
|
||||
@@ -1545,7 +1591,7 @@ func (a *App) addClientMount() error {
|
||||
return err
|
||||
}
|
||||
|
||||
cfg, err := a.collectCIFSMountConfig(CIFSMountConfig{})
|
||||
cfg, err := a.collectCIFSMountConfig(defaultCIFSMountConfig())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
58
app_test.go
58
app_test.go
@@ -1,6 +1,9 @@
|
||||
package main
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"os/user"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseCIFSMountEntries(t *testing.T) {
|
||||
contents := `# comment
|
||||
@@ -86,3 +89,56 @@ func TestUpdateFstabContentsAddEditDelete(t *testing.T) {
|
||||
t.Fatalf("unexpected delete result:\nwant: %q\ngot: %q", wantDeleted, deleted)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultLocalMountOwnerGroupUsesCurrentUser(t *testing.T) {
|
||||
getenv := func(string) string { return "" }
|
||||
currentUser := func() (*user.User, error) {
|
||||
return &user.User{Username: "alice", Gid: "1000"}, nil
|
||||
}
|
||||
lookupUser := func(string) (*user.User, error) {
|
||||
t.Fatal("lookupUser should not be called without SUDO_USER")
|
||||
return nil, nil
|
||||
}
|
||||
lookupGroupID := func(id string) (*user.Group, error) {
|
||||
if id != "1000" {
|
||||
t.Fatalf("unexpected group id lookup: %q", id)
|
||||
}
|
||||
return &user.Group{Name: "alice"}, nil
|
||||
}
|
||||
|
||||
uid, gid := defaultLocalMountOwnerGroup(getenv, currentUser, lookupUser, lookupGroupID)
|
||||
if uid != "alice" || gid != "alice" {
|
||||
t.Fatalf("unexpected defaults: uid=%q gid=%q", uid, gid)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultLocalMountOwnerGroupUsesSudoUser(t *testing.T) {
|
||||
getenv := func(key string) string {
|
||||
switch key {
|
||||
case "SUDO_USER":
|
||||
return "carol"
|
||||
case "SUDO_GID":
|
||||
return "2000"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
currentUser := func() (*user.User, error) {
|
||||
return &user.User{Username: "root", Gid: "0"}, nil
|
||||
}
|
||||
lookupUser := func(string) (*user.User, error) {
|
||||
t.Fatal("lookupUser should not be called when SUDO_GID is set")
|
||||
return nil, nil
|
||||
}
|
||||
lookupGroupID := func(id string) (*user.Group, error) {
|
||||
if id != "2000" {
|
||||
t.Fatalf("unexpected group id lookup: %q", id)
|
||||
}
|
||||
return &user.Group{Name: "developers"}, nil
|
||||
}
|
||||
|
||||
uid, gid := defaultLocalMountOwnerGroup(getenv, currentUser, lookupUser, lookupGroupID)
|
||||
if uid != "carol" || gid != "developers" {
|
||||
t.Fatalf("unexpected defaults: uid=%q gid=%q", uid, gid)
|
||||
}
|
||||
}
|
||||
|
||||
BIN
samba-configer
BIN
samba-configer
Binary file not shown.
129
tui.go
129
tui.go
@@ -12,7 +12,9 @@ import (
|
||||
|
||||
type tuiTheme struct {
|
||||
outer lipgloss.Style
|
||||
headerBar lipgloss.Style
|
||||
badge lipgloss.Style
|
||||
chrome lipgloss.Style
|
||||
title lipgloss.Style
|
||||
subtitle lipgloss.Style
|
||||
sectionTitle lipgloss.Style
|
||||
@@ -20,10 +22,15 @@ type tuiTheme struct {
|
||||
muted lipgloss.Style
|
||||
footer lipgloss.Style
|
||||
panel lipgloss.Style
|
||||
panelAccent lipgloss.Style
|
||||
row lipgloss.Style
|
||||
rowSelected lipgloss.Style
|
||||
rowNumber lipgloss.Style
|
||||
rowNumberOn lipgloss.Style
|
||||
rowKey lipgloss.Style
|
||||
rowKeySelected lipgloss.Style
|
||||
rowTitle lipgloss.Style
|
||||
rowTitleOn lipgloss.Style
|
||||
rowHint lipgloss.Style
|
||||
rowHintActive lipgloss.Style
|
||||
statusInfo lipgloss.Style
|
||||
@@ -34,35 +41,44 @@ type tuiTheme struct {
|
||||
}
|
||||
|
||||
func newTUITheme() tuiTheme {
|
||||
border := lipgloss.Color("#4B5D6B")
|
||||
text := lipgloss.Color("#E9E1D4")
|
||||
muted := lipgloss.Color("#9E9385")
|
||||
accent := lipgloss.Color("#D97745")
|
||||
accentSoft := lipgloss.Color("#F4D8BD")
|
||||
panel := lipgloss.Color("#192126")
|
||||
panelAlt := lipgloss.Color("#232E36")
|
||||
info := lipgloss.Color("#7FB0C2")
|
||||
warn := lipgloss.Color("#D7A55A")
|
||||
success := lipgloss.Color("#8AAC83")
|
||||
border := lipgloss.Color("#5A6772")
|
||||
borderSoft := lipgloss.Color("#31414A")
|
||||
text := lipgloss.Color("#ECE4D8")
|
||||
muted := lipgloss.Color("#A49788")
|
||||
accent := lipgloss.Color("#D9733F")
|
||||
accentSoft := lipgloss.Color("#F2D2B5")
|
||||
panel := lipgloss.Color("#161C20")
|
||||
panelAlt := lipgloss.Color("#212A30")
|
||||
panelLift := lipgloss.Color("#2B363D")
|
||||
info := lipgloss.Color("#86B7C6")
|
||||
warn := lipgloss.Color("#D8AB63")
|
||||
success := lipgloss.Color("#91B57F")
|
||||
|
||||
return tuiTheme{
|
||||
outer: lipgloss.NewStyle().
|
||||
Border(lipgloss.RoundedBorder()).
|
||||
BorderForeground(border).
|
||||
Padding(1, 2),
|
||||
headerBar: lipgloss.NewStyle().
|
||||
Foreground(borderSoft),
|
||||
chrome: lipgloss.NewStyle().
|
||||
Foreground(muted),
|
||||
badge: lipgloss.NewStyle().
|
||||
Foreground(panel).
|
||||
Background(accentSoft).
|
||||
Bold(true).
|
||||
Padding(0, 1),
|
||||
subtitle: lipgloss.NewStyle().
|
||||
Foreground(muted),
|
||||
title: lipgloss.NewStyle().
|
||||
Foreground(text).
|
||||
Bold(true),
|
||||
subtitle: lipgloss.NewStyle().
|
||||
Foreground(muted),
|
||||
sectionTitle: lipgloss.NewStyle().
|
||||
Foreground(accentSoft).
|
||||
Bold(true).
|
||||
Border(lipgloss.NormalBorder(), false, false, true, false).
|
||||
BorderForeground(borderSoft).
|
||||
PaddingBottom(0).
|
||||
MarginBottom(1),
|
||||
body: lipgloss.NewStyle().
|
||||
Foreground(text),
|
||||
@@ -71,26 +87,50 @@ func newTUITheme() tuiTheme {
|
||||
footer: lipgloss.NewStyle().
|
||||
Foreground(muted).
|
||||
Border(lipgloss.NormalBorder(), true, false, false, false).
|
||||
BorderForeground(border).
|
||||
PaddingTop(1),
|
||||
BorderForeground(borderSoft).
|
||||
PaddingTop(1).
|
||||
MarginTop(1),
|
||||
panel: lipgloss.NewStyle().
|
||||
Border(lipgloss.NormalBorder(), false, false, false, true).
|
||||
BorderForeground(border).
|
||||
PaddingLeft(1),
|
||||
row: lipgloss.NewStyle().
|
||||
BorderForeground(borderSoft).
|
||||
PaddingLeft(1).
|
||||
MarginBottom(1),
|
||||
panelAccent: lipgloss.NewStyle().
|
||||
Border(lipgloss.ThickBorder(), false, false, false, true).
|
||||
BorderForeground(panelAlt).
|
||||
PaddingLeft(1),
|
||||
BorderForeground(accent).
|
||||
PaddingLeft(1).
|
||||
MarginBottom(1),
|
||||
row: lipgloss.NewStyle().
|
||||
Border(lipgloss.NormalBorder(), false, false, false, true).
|
||||
BorderForeground(panelLift).
|
||||
Padding(0, 1).
|
||||
MarginBottom(1),
|
||||
rowSelected: lipgloss.NewStyle().
|
||||
Border(lipgloss.ThickBorder(), false, false, false, true).
|
||||
BorderForeground(accent).
|
||||
Background(panel).
|
||||
PaddingLeft(1),
|
||||
Padding(0, 1).
|
||||
MarginBottom(1),
|
||||
rowNumber: lipgloss.NewStyle().
|
||||
Foreground(muted),
|
||||
rowNumberOn: lipgloss.NewStyle().
|
||||
Foreground(accentSoft).
|
||||
Bold(true),
|
||||
rowKey: lipgloss.NewStyle().
|
||||
Foreground(accent).
|
||||
Bold(true),
|
||||
Bold(true).
|
||||
Background(panelAlt).
|
||||
Padding(0, 1),
|
||||
rowKeySelected: lipgloss.NewStyle().
|
||||
Foreground(accentSoft).
|
||||
Background(panelLift).
|
||||
Bold(true).
|
||||
Padding(0, 1),
|
||||
rowTitle: lipgloss.NewStyle().
|
||||
Foreground(text).
|
||||
Bold(true),
|
||||
rowTitleOn: lipgloss.NewStyle().
|
||||
Foreground(text).
|
||||
Bold(true),
|
||||
rowHint: lipgloss.NewStyle().
|
||||
Foreground(muted),
|
||||
@@ -111,7 +151,8 @@ func newTUITheme() tuiTheme {
|
||||
inputBox: lipgloss.NewStyle().
|
||||
Border(lipgloss.RoundedBorder()).
|
||||
BorderForeground(border).
|
||||
Padding(0, 1),
|
||||
Padding(0, 1).
|
||||
Background(panelAlt),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,10 +185,11 @@ func (t tuiTheme) renderFrame(width int, title, subtitle, body, footer string) s
|
||||
placeWidth = frameWidth
|
||||
}
|
||||
|
||||
sections := []string{
|
||||
t.badge.Render("SAMBA CONFIGER"),
|
||||
t.title.Width(contentWidth).Render(title),
|
||||
}
|
||||
headerLeft := lipgloss.JoinHorizontal(lipgloss.Top, t.badge.Render("SAMBA CONFIGER"), " ", t.chrome.Render("share editor"))
|
||||
headerRule := t.headerBar.Render(strings.Repeat("─", max(0, contentWidth-lipgloss.Width(headerLeft)-1)))
|
||||
header := lipgloss.JoinHorizontal(lipgloss.Center, headerLeft, " ", headerRule)
|
||||
|
||||
sections := []string{header, t.title.Width(contentWidth).Render(title)}
|
||||
if subtitle != "" {
|
||||
sections = append(sections, t.subtitle.Width(contentWidth).Render(subtitle))
|
||||
}
|
||||
@@ -172,7 +214,7 @@ func (t tuiTheme) renderSection(width int, title string, lines []string) string
|
||||
rendered = append(rendered, t.sectionTitle.Width(contentWidth).Render(title))
|
||||
}
|
||||
for _, line := range lines {
|
||||
rendered = append(rendered, t.panel.Width(contentWidth).Render(t.body.Width(contentWidth-2).Render(line)))
|
||||
rendered = append(rendered, t.panel.Width(contentWidth).Render(t.body.Width(max(1, contentWidth-2)).Render(line)))
|
||||
}
|
||||
return strings.Join(rendered, "\n")
|
||||
}
|
||||
@@ -190,6 +232,13 @@ func (t tuiTheme) renderMessage(width int, kind, text string) string {
|
||||
)
|
||||
}
|
||||
|
||||
func max(a, b int) int {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
type menuOption struct {
|
||||
Key string
|
||||
Value string
|
||||
@@ -260,17 +309,29 @@ func (m menuModel) View() string {
|
||||
for i, option := range m.options {
|
||||
rowStyle := m.theme.row
|
||||
keyStyle := m.theme.rowKey
|
||||
titleStyle := m.theme.rowTitle
|
||||
hintStyle := m.theme.rowHint
|
||||
prefix := m.theme.muted.Render(fmt.Sprintf("%02d", i+1))
|
||||
numberStyle := m.theme.rowNumber
|
||||
prefix := numberStyle.Render(fmt.Sprintf("%02d", i+1))
|
||||
if i == m.cursor {
|
||||
rowStyle = m.theme.rowSelected
|
||||
keyStyle = m.theme.rowKeySelected
|
||||
titleStyle = m.theme.rowTitleOn
|
||||
hintStyle = m.theme.rowHintActive
|
||||
prefix = keyStyle.Render(">>")
|
||||
numberStyle = m.theme.rowNumberOn
|
||||
prefix = numberStyle.Render(">>")
|
||||
}
|
||||
|
||||
label := fmt.Sprintf("%s %s %s", prefix, keyStyle.Render("["+option.Key+"]"), option.Label)
|
||||
rowLines := []string{label}
|
||||
top := lipgloss.JoinHorizontal(
|
||||
lipgloss.Center,
|
||||
prefix,
|
||||
" ",
|
||||
keyStyle.Render(strings.ToUpper(option.Key)),
|
||||
" ",
|
||||
titleStyle.Render(option.Label),
|
||||
)
|
||||
|
||||
rowLines := []string{top}
|
||||
if option.Description != "" {
|
||||
rowLines = append(rowLines, hintStyle.Render(option.Description))
|
||||
}
|
||||
@@ -360,10 +421,8 @@ func (m textPromptModel) View() string {
|
||||
width := clampFrameWidth(m.width)
|
||||
contentWidth := innerWidth(width)
|
||||
|
||||
lines := []string{
|
||||
m.theme.inputLabel.Width(contentWidth).Render(m.label),
|
||||
m.theme.inputBox.Width(contentWidth).Render(m.input.View()),
|
||||
}
|
||||
lines := []string{m.theme.inputLabel.Width(contentWidth).Render(m.label)}
|
||||
lines = append(lines, m.theme.panelAccent.Width(contentWidth).Render(m.theme.inputBox.Width(max(8, contentWidth-2)).Render(m.input.View())))
|
||||
if m.defaultValue != "" {
|
||||
lines = append(lines, m.theme.muted.Width(contentWidth).Render("Enter keeps: "+m.defaultValue))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user