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
|
}, 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 {
|
func (a *App) ensureMountPoint(path string) error {
|
||||||
info, err := os.Stat(path)
|
info, err := os.Stat(path)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@@ -1545,7 +1591,7 @@ func (a *App) addClientMount() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg, err := a.collectCIFSMountConfig(CIFSMountConfig{})
|
cfg, err := a.collectCIFSMountConfig(defaultCIFSMountConfig())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
58
app_test.go
58
app_test.go
@@ -1,6 +1,9 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"os/user"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
func TestParseCIFSMountEntries(t *testing.T) {
|
func TestParseCIFSMountEntries(t *testing.T) {
|
||||||
contents := `# comment
|
contents := `# comment
|
||||||
@@ -86,3 +89,56 @@ func TestUpdateFstabContentsAddEditDelete(t *testing.T) {
|
|||||||
t.Fatalf("unexpected delete result:\nwant: %q\ngot: %q", wantDeleted, deleted)
|
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 {
|
type tuiTheme struct {
|
||||||
outer lipgloss.Style
|
outer lipgloss.Style
|
||||||
|
headerBar lipgloss.Style
|
||||||
badge lipgloss.Style
|
badge lipgloss.Style
|
||||||
|
chrome lipgloss.Style
|
||||||
title lipgloss.Style
|
title lipgloss.Style
|
||||||
subtitle lipgloss.Style
|
subtitle lipgloss.Style
|
||||||
sectionTitle lipgloss.Style
|
sectionTitle lipgloss.Style
|
||||||
@@ -20,10 +22,15 @@ type tuiTheme struct {
|
|||||||
muted lipgloss.Style
|
muted lipgloss.Style
|
||||||
footer lipgloss.Style
|
footer lipgloss.Style
|
||||||
panel lipgloss.Style
|
panel lipgloss.Style
|
||||||
|
panelAccent lipgloss.Style
|
||||||
row lipgloss.Style
|
row lipgloss.Style
|
||||||
rowSelected lipgloss.Style
|
rowSelected lipgloss.Style
|
||||||
|
rowNumber lipgloss.Style
|
||||||
|
rowNumberOn lipgloss.Style
|
||||||
rowKey lipgloss.Style
|
rowKey lipgloss.Style
|
||||||
rowKeySelected lipgloss.Style
|
rowKeySelected lipgloss.Style
|
||||||
|
rowTitle lipgloss.Style
|
||||||
|
rowTitleOn lipgloss.Style
|
||||||
rowHint lipgloss.Style
|
rowHint lipgloss.Style
|
||||||
rowHintActive lipgloss.Style
|
rowHintActive lipgloss.Style
|
||||||
statusInfo lipgloss.Style
|
statusInfo lipgloss.Style
|
||||||
@@ -34,35 +41,44 @@ type tuiTheme struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func newTUITheme() tuiTheme {
|
func newTUITheme() tuiTheme {
|
||||||
border := lipgloss.Color("#4B5D6B")
|
border := lipgloss.Color("#5A6772")
|
||||||
text := lipgloss.Color("#E9E1D4")
|
borderSoft := lipgloss.Color("#31414A")
|
||||||
muted := lipgloss.Color("#9E9385")
|
text := lipgloss.Color("#ECE4D8")
|
||||||
accent := lipgloss.Color("#D97745")
|
muted := lipgloss.Color("#A49788")
|
||||||
accentSoft := lipgloss.Color("#F4D8BD")
|
accent := lipgloss.Color("#D9733F")
|
||||||
panel := lipgloss.Color("#192126")
|
accentSoft := lipgloss.Color("#F2D2B5")
|
||||||
panelAlt := lipgloss.Color("#232E36")
|
panel := lipgloss.Color("#161C20")
|
||||||
info := lipgloss.Color("#7FB0C2")
|
panelAlt := lipgloss.Color("#212A30")
|
||||||
warn := lipgloss.Color("#D7A55A")
|
panelLift := lipgloss.Color("#2B363D")
|
||||||
success := lipgloss.Color("#8AAC83")
|
info := lipgloss.Color("#86B7C6")
|
||||||
|
warn := lipgloss.Color("#D8AB63")
|
||||||
|
success := lipgloss.Color("#91B57F")
|
||||||
|
|
||||||
return tuiTheme{
|
return tuiTheme{
|
||||||
outer: lipgloss.NewStyle().
|
outer: lipgloss.NewStyle().
|
||||||
Border(lipgloss.RoundedBorder()).
|
Border(lipgloss.RoundedBorder()).
|
||||||
BorderForeground(border).
|
BorderForeground(border).
|
||||||
Padding(1, 2),
|
Padding(1, 2),
|
||||||
|
headerBar: lipgloss.NewStyle().
|
||||||
|
Foreground(borderSoft),
|
||||||
|
chrome: lipgloss.NewStyle().
|
||||||
|
Foreground(muted),
|
||||||
badge: lipgloss.NewStyle().
|
badge: lipgloss.NewStyle().
|
||||||
Foreground(panel).
|
Foreground(panel).
|
||||||
Background(accentSoft).
|
Background(accentSoft).
|
||||||
Bold(true).
|
Bold(true).
|
||||||
Padding(0, 1),
|
Padding(0, 1),
|
||||||
|
subtitle: lipgloss.NewStyle().
|
||||||
|
Foreground(muted),
|
||||||
title: lipgloss.NewStyle().
|
title: lipgloss.NewStyle().
|
||||||
Foreground(text).
|
Foreground(text).
|
||||||
Bold(true),
|
Bold(true),
|
||||||
subtitle: lipgloss.NewStyle().
|
|
||||||
Foreground(muted),
|
|
||||||
sectionTitle: lipgloss.NewStyle().
|
sectionTitle: lipgloss.NewStyle().
|
||||||
Foreground(accentSoft).
|
Foreground(accentSoft).
|
||||||
Bold(true).
|
Bold(true).
|
||||||
|
Border(lipgloss.NormalBorder(), false, false, true, false).
|
||||||
|
BorderForeground(borderSoft).
|
||||||
|
PaddingBottom(0).
|
||||||
MarginBottom(1),
|
MarginBottom(1),
|
||||||
body: lipgloss.NewStyle().
|
body: lipgloss.NewStyle().
|
||||||
Foreground(text),
|
Foreground(text),
|
||||||
@@ -71,26 +87,50 @@ func newTUITheme() tuiTheme {
|
|||||||
footer: lipgloss.NewStyle().
|
footer: lipgloss.NewStyle().
|
||||||
Foreground(muted).
|
Foreground(muted).
|
||||||
Border(lipgloss.NormalBorder(), true, false, false, false).
|
Border(lipgloss.NormalBorder(), true, false, false, false).
|
||||||
BorderForeground(border).
|
BorderForeground(borderSoft).
|
||||||
PaddingTop(1),
|
PaddingTop(1).
|
||||||
|
MarginTop(1),
|
||||||
panel: lipgloss.NewStyle().
|
panel: lipgloss.NewStyle().
|
||||||
Border(lipgloss.NormalBorder(), false, false, false, true).
|
Border(lipgloss.NormalBorder(), false, false, false, true).
|
||||||
BorderForeground(border).
|
BorderForeground(borderSoft).
|
||||||
PaddingLeft(1),
|
PaddingLeft(1).
|
||||||
row: lipgloss.NewStyle().
|
MarginBottom(1),
|
||||||
|
panelAccent: lipgloss.NewStyle().
|
||||||
Border(lipgloss.ThickBorder(), false, false, false, true).
|
Border(lipgloss.ThickBorder(), false, false, false, true).
|
||||||
BorderForeground(panelAlt).
|
BorderForeground(accent).
|
||||||
PaddingLeft(1),
|
PaddingLeft(1).
|
||||||
|
MarginBottom(1),
|
||||||
|
row: lipgloss.NewStyle().
|
||||||
|
Border(lipgloss.NormalBorder(), false, false, false, true).
|
||||||
|
BorderForeground(panelLift).
|
||||||
|
Padding(0, 1).
|
||||||
|
MarginBottom(1),
|
||||||
rowSelected: lipgloss.NewStyle().
|
rowSelected: lipgloss.NewStyle().
|
||||||
Border(lipgloss.ThickBorder(), false, false, false, true).
|
Border(lipgloss.ThickBorder(), false, false, false, true).
|
||||||
BorderForeground(accent).
|
BorderForeground(accent).
|
||||||
Background(panel).
|
Background(panel).
|
||||||
PaddingLeft(1),
|
Padding(0, 1).
|
||||||
|
MarginBottom(1),
|
||||||
|
rowNumber: lipgloss.NewStyle().
|
||||||
|
Foreground(muted),
|
||||||
|
rowNumberOn: lipgloss.NewStyle().
|
||||||
|
Foreground(accentSoft).
|
||||||
|
Bold(true),
|
||||||
rowKey: lipgloss.NewStyle().
|
rowKey: lipgloss.NewStyle().
|
||||||
Foreground(accent).
|
Foreground(accent).
|
||||||
Bold(true),
|
Bold(true).
|
||||||
|
Background(panelAlt).
|
||||||
|
Padding(0, 1),
|
||||||
rowKeySelected: lipgloss.NewStyle().
|
rowKeySelected: lipgloss.NewStyle().
|
||||||
Foreground(accentSoft).
|
Foreground(accentSoft).
|
||||||
|
Background(panelLift).
|
||||||
|
Bold(true).
|
||||||
|
Padding(0, 1),
|
||||||
|
rowTitle: lipgloss.NewStyle().
|
||||||
|
Foreground(text).
|
||||||
|
Bold(true),
|
||||||
|
rowTitleOn: lipgloss.NewStyle().
|
||||||
|
Foreground(text).
|
||||||
Bold(true),
|
Bold(true),
|
||||||
rowHint: lipgloss.NewStyle().
|
rowHint: lipgloss.NewStyle().
|
||||||
Foreground(muted),
|
Foreground(muted),
|
||||||
@@ -111,7 +151,8 @@ func newTUITheme() tuiTheme {
|
|||||||
inputBox: lipgloss.NewStyle().
|
inputBox: lipgloss.NewStyle().
|
||||||
Border(lipgloss.RoundedBorder()).
|
Border(lipgloss.RoundedBorder()).
|
||||||
BorderForeground(border).
|
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
|
placeWidth = frameWidth
|
||||||
}
|
}
|
||||||
|
|
||||||
sections := []string{
|
headerLeft := lipgloss.JoinHorizontal(lipgloss.Top, t.badge.Render("SAMBA CONFIGER"), " ", t.chrome.Render("share editor"))
|
||||||
t.badge.Render("SAMBA CONFIGER"),
|
headerRule := t.headerBar.Render(strings.Repeat("─", max(0, contentWidth-lipgloss.Width(headerLeft)-1)))
|
||||||
t.title.Width(contentWidth).Render(title),
|
header := lipgloss.JoinHorizontal(lipgloss.Center, headerLeft, " ", headerRule)
|
||||||
}
|
|
||||||
|
sections := []string{header, t.title.Width(contentWidth).Render(title)}
|
||||||
if subtitle != "" {
|
if subtitle != "" {
|
||||||
sections = append(sections, t.subtitle.Width(contentWidth).Render(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))
|
rendered = append(rendered, t.sectionTitle.Width(contentWidth).Render(title))
|
||||||
}
|
}
|
||||||
for _, line := range lines {
|
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")
|
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 {
|
type menuOption struct {
|
||||||
Key string
|
Key string
|
||||||
Value string
|
Value string
|
||||||
@@ -260,17 +309,29 @@ func (m menuModel) View() string {
|
|||||||
for i, option := range m.options {
|
for i, option := range m.options {
|
||||||
rowStyle := m.theme.row
|
rowStyle := m.theme.row
|
||||||
keyStyle := m.theme.rowKey
|
keyStyle := m.theme.rowKey
|
||||||
|
titleStyle := m.theme.rowTitle
|
||||||
hintStyle := m.theme.rowHint
|
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 {
|
if i == m.cursor {
|
||||||
rowStyle = m.theme.rowSelected
|
rowStyle = m.theme.rowSelected
|
||||||
keyStyle = m.theme.rowKeySelected
|
keyStyle = m.theme.rowKeySelected
|
||||||
|
titleStyle = m.theme.rowTitleOn
|
||||||
hintStyle = m.theme.rowHintActive
|
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)
|
top := lipgloss.JoinHorizontal(
|
||||||
rowLines := []string{label}
|
lipgloss.Center,
|
||||||
|
prefix,
|
||||||
|
" ",
|
||||||
|
keyStyle.Render(strings.ToUpper(option.Key)),
|
||||||
|
" ",
|
||||||
|
titleStyle.Render(option.Label),
|
||||||
|
)
|
||||||
|
|
||||||
|
rowLines := []string{top}
|
||||||
if option.Description != "" {
|
if option.Description != "" {
|
||||||
rowLines = append(rowLines, hintStyle.Render(option.Description))
|
rowLines = append(rowLines, hintStyle.Render(option.Description))
|
||||||
}
|
}
|
||||||
@@ -360,10 +421,8 @@ func (m textPromptModel) View() string {
|
|||||||
width := clampFrameWidth(m.width)
|
width := clampFrameWidth(m.width)
|
||||||
contentWidth := innerWidth(width)
|
contentWidth := innerWidth(width)
|
||||||
|
|
||||||
lines := []string{
|
lines := []string{m.theme.inputLabel.Width(contentWidth).Render(m.label)}
|
||||||
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())))
|
||||||
m.theme.inputBox.Width(contentWidth).Render(m.input.View()),
|
|
||||||
}
|
|
||||||
if m.defaultValue != "" {
|
if m.defaultValue != "" {
|
||||||
lines = append(lines, m.theme.muted.Width(contentWidth).Render("Enter keeps: "+m.defaultValue))
|
lines = append(lines, m.theme.muted.Width(contentWidth).Render("Enter keeps: "+m.defaultValue))
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user