repos / pico

pico services - prose.sh, pastes.sh, imgs.sh, feeds.sh, pgs.sh
git clone https://github.com/picosh/pico.git

commit
e94d326
parent
c2f5b9e
author
Eric Bower
date
2024-02-27 17:14:33 +0000 UTC
design(pgs): cli aesthetic
4 files changed,  +155, -67
M db/postgres/storage.go
+1, -1
1@@ -264,7 +264,7 @@ const (
2 	LEFT JOIN app_users ON app_users.id = projects.user_id
3 	ORDER BY created_at ASC
4 	LIMIT $1 OFFSET $2`
5-	sqlFindProjectsByUser   = `SELECT id, user_id, name, project_dir, acl, created_at, updated_at FROM projects WHERE user_id = $1 ORDER BY updated_at DESC;`
6+	sqlFindProjectsByUser   = `SELECT id, user_id, name, project_dir, acl, created_at, updated_at FROM projects WHERE user_id = $1 ORDER BY name ASC, updated_at DESC;`
7 	sqlFindProjectsByPrefix = `SELECT id, user_id, name, project_dir, acl, created_at, updated_at FROM projects WHERE user_id = $1 AND name = project_dir AND name ILIKE $2 ORDER BY updated_at ASC, name ASC;`
8 	sqlFindProjectLinks     = `SELECT id, user_id, name, project_dir, acl, created_at, updated_at FROM projects WHERE user_id = $1 AND name != project_dir AND project_dir = $2 ORDER BY name ASC;`
9 	sqlLinkToProject        = `UPDATE projects SET project_dir = $1, updated_at = $2 WHERE id = $3;`
M pgs/cli.go
+130, -42
  1@@ -9,29 +9,122 @@ import (
  2 	"path/filepath"
  3 	"strings"
  4 
  5+	"github.com/charmbracelet/lipgloss"
  6+	"github.com/charmbracelet/lipgloss/table"
  7 	"github.com/picosh/pico/db"
  8 	"github.com/picosh/pico/shared"
  9 	"github.com/picosh/pico/shared/storage"
 10+	"github.com/picosh/pico/wish/cms/ui/common"
 11 )
 12 
 13+var re = lipgloss.NewRenderer(os.Stdout)
 14+var baseStyle = re.NewStyle().Padding(0, 1)
 15+var headerStyle = baseStyle.Copy().Foreground(common.Indigo).Bold(true)
 16+var borderStyle = re.NewStyle().Foreground(lipgloss.Color("238"))
 17+
 18+func styleRows(row, col int) lipgloss.Style {
 19+	if row == 0 {
 20+		return headerStyle
 21+	}
 22+
 23+	even := row%2 == 0
 24+	if even {
 25+		return baseStyle.Copy().Foreground(lipgloss.Color("245"))
 26+	}
 27+	return baseStyle.Copy().Foreground(lipgloss.Color("252"))
 28+}
 29+
 30+func projectTable(projects []*db.Project) *table.Table {
 31+	headers := []string{
 32+		"Name",
 33+		"Last Updated",
 34+		"Links To",
 35+		"ACL Type",
 36+		"ACL",
 37+	}
 38+	data := [][]string{}
 39+	for _, project := range projects {
 40+		row := []string{
 41+			project.Name,
 42+			project.UpdatedAt.Format("2006-01-02 15:04:05"),
 43+		}
 44+		links := ""
 45+		if project.ProjectDir != project.Name {
 46+			links = project.ProjectDir
 47+		}
 48+		row = append(row, links)
 49+		row = append(row,
 50+			project.Acl.Type,
 51+			strings.Join(project.Acl.Data, " "),
 52+		)
 53+		data = append(data, row)
 54+	}
 55+
 56+	t := table.New().
 57+		Border(lipgloss.NormalBorder()).
 58+		BorderStyle(borderStyle).
 59+		Headers(headers...).
 60+		Rows(data...).
 61+		StyleFunc(styleRows)
 62+	return t
 63+}
 64+
 65 func getHelpText(userName, projectName string) string {
 66-	helpStr := "commands: [help, stats, ls, rm, link, unlink, prune, retain, depends]\n\n"
 67-	helpStr += "NOTICE: any cmd that results in a mutation *must* be appended with `--write` for the changes to persist, otherwise it will simply output a dry-run.\n\n"
 68-
 69-	sshCmdStr := fmt.Sprintf("ssh %s@pgs.sh", userName)
 70-	helpStr += fmt.Sprintf("`%s help`: prints this screen\n", sshCmdStr)
 71-	helpStr += fmt.Sprintf("`%s stats`: prints stats for user\n", sshCmdStr)
 72-	helpStr += fmt.Sprintf("`%s ls`: lists projects\n", sshCmdStr)
 73-	helpStr += fmt.Sprintf("`%s rm %s`: deletes `%s`\n", sshCmdStr, projectName, projectName)
 74-	helpStr += fmt.Sprintf("`%s link %s project-b`: symbolic link from `%s` to `project-b`\n", sshCmdStr, projectName, projectName)
 75-	helpStr += fmt.Sprintf(
 76-		"`%s unlink %s`: alias for `link %s %s`, which removes symbolic link for `%s`\n",
 77-		sshCmdStr, projectName, projectName, projectName, projectName,
 78-	)
 79-	helpStr += fmt.Sprintf("`%s prune %s`: removes all projects that match prefix `%s` and is not linked to another project\n", sshCmdStr, projectName, projectName)
 80-	helpStr += fmt.Sprintf("`%s retain %s`: alias for `prune` but retains the (3) most recently updated projects\n", sshCmdStr, projectName)
 81-	helpStr += fmt.Sprintf("`%s depends %s`: lists all projects linked to `%s`\n", sshCmdStr, projectName, projectName)
 82-	helpStr += fmt.Sprintf("`%s acl %s [public, public_keys, pico] [comma delimited shasum keys or pico usernames]`: control access to project `%s`\n", sshCmdStr, projectName, projectName)
 83+	helpStr := "Commands: [help, stats, ls, rm, link, unlink, prune, retain, depends, acl]\n\n"
 84+	helpStr += "NOTICE: *must* append with `--write` for the changes to persist.\n\n"
 85+
 86+	headers := []string{"Cmd", "Description"}
 87+	data := [][]string{
 88+		{
 89+			"help",
 90+			"prints this screen",
 91+		},
 92+		{
 93+			"stats",
 94+			"usage statistics",
 95+		},
 96+		{
 97+			"ls",
 98+			"lists projects",
 99+		},
100+		{
101+			fmt.Sprintf("rm %s", projectName),
102+			fmt.Sprintf("delete %s", projectName),
103+		},
104+		{
105+			fmt.Sprintf("link %s project-b", projectName),
106+			fmt.Sprintf("symbolic link `%s` to `project-b`", projectName),
107+		},
108+		{
109+			fmt.Sprintf("unlink %s", projectName),
110+			fmt.Sprintf("removes symbolic link for `%s`", projectName),
111+		},
112+		{
113+			fmt.Sprintf("prune %s", projectName),
114+			fmt.Sprintf("removes projects that match prefix `%s`", projectName),
115+		},
116+		{
117+			fmt.Sprintf("retain %s", projectName),
118+			"alias `prune` but keeps last (3) projects",
119+		},
120+		{
121+			fmt.Sprintf("depends %s", projectName),
122+			fmt.Sprintf("lists all projects linked to `%s`", projectName),
123+		},
124+		{
125+			fmt.Sprintf("acl %s", projectName),
126+			fmt.Sprintf("access control for `%s`", projectName),
127+		},
128+	}
129+
130+	t := table.New().
131+		Border(lipgloss.NormalBorder()).
132+		BorderStyle(borderStyle).
133+		Headers(headers...).
134+		Rows(data...).
135+		StyleFunc(styleRows)
136+
137+	helpStr += t.String()
138 	return helpStr
139 }
140 
141@@ -170,16 +263,21 @@ func (c *Cmd) stats(cfgMaxSize uint64) error {
142 		return err
143 	}
144 
145-	str := "stats\n"
146-	str += "=====\n"
147-	str += fmt.Sprintf(
148-		"space:\t\t%.4f/%.4fGB, %.4f%%\n",
149-		shared.BytesToGB(int(totalFileSize)),
150-		shared.BytesToGB(int(storageMax)),
151-		(float32(totalFileSize)/float32(storageMax))*100,
152-	)
153-	str += fmt.Sprintf("projects:\t%d", len(projects))
154-	c.output(str)
155+	headers := []string{"Used (GB)", "Quota (GB)", "Used (%)", "Projects (#)"}
156+	data := []string{
157+		fmt.Sprintf("%.4f", shared.BytesToGB(int(totalFileSize))),
158+		fmt.Sprintf("%.4f", shared.BytesToGB(int(storageMax))),
159+		fmt.Sprintf("%.4f", (float32(totalFileSize)/float32(storageMax))*100),
160+		fmt.Sprintf("%d", len(projects)),
161+	}
162+
163+	t := table.New().
164+		Border(lipgloss.NormalBorder()).
165+		BorderStyle(borderStyle).
166+		Headers(headers...).
167+		Rows(data).
168+		StyleFunc(styleRows)
169+	c.output(t.String())
170 
171 	return nil
172 }
173@@ -194,13 +292,8 @@ func (c *Cmd) ls() error {
174 		c.output("no projects found")
175 	}
176 
177-	for _, project := range projects {
178-		out := fmt.Sprintf("%s (links to: %s)", project.Name, project.ProjectDir)
179-		if project.Name == project.ProjectDir {
180-			out = fmt.Sprintf("%s\t(last updated: %s)", project.Name, project.UpdatedAt)
181-		}
182-		c.output(out)
183-	}
184+	t := projectTable(projects)
185+	c.output(t.String())
186 
187 	return nil
188 }
189@@ -280,18 +373,13 @@ func (c *Cmd) depends(projectName string) error {
190 	}
191 
192 	if len(projects) == 0 {
193-		out := fmt.Sprintf("no projects linked to this project (%s) found", projectName)
194+		out := fmt.Sprintf("no projects linked to (%s)", projectName)
195 		c.output(out)
196 		return nil
197 	}
198 
199-	for _, project := range projects {
200-		out := fmt.Sprintf("%s (links to: %s)", project.Name, project.ProjectDir)
201-		if project.Name == project.ProjectDir {
202-			out = project.Name
203-		}
204-		c.output(out)
205-	}
206+	t := projectTable(projects)
207+	c.output(t.String())
208 
209 	return nil
210 }
M wish/cms/ui/common/styles.go
+21, -21
 1@@ -6,13 +6,13 @@ import (
 2 
 3 // Color definitions.
 4 var (
 5-	indigo       = lipgloss.AdaptiveColor{Light: "#5A56E0", Dark: "#7571F9"}
 6-	subtleIndigo = lipgloss.AdaptiveColor{Light: "#7D79F6", Dark: "#514DC1"}
 7-	cream        = lipgloss.AdaptiveColor{Light: "#FFFDF5", Dark: "#FFFDF5"}
 8-	fuschia      = lipgloss.AdaptiveColor{Light: "#EE6FF8", Dark: "#EE6FF8"}
 9-	green        = lipgloss.Color("#04B575")
10-	red          = lipgloss.AdaptiveColor{Light: "#FF4672", Dark: "#ED567A"}
11-	faintRed     = lipgloss.AdaptiveColor{Light: "#FF6F91", Dark: "#C74665"}
12+	Indigo       = lipgloss.AdaptiveColor{Light: "#5A56E0", Dark: "#7571F9"}
13+	SubtleIndigo = lipgloss.AdaptiveColor{Light: "#7D79F6", Dark: "#514DC1"}
14+	Cream        = lipgloss.AdaptiveColor{Light: "#FFFDF5", Dark: "#FFFDF5"}
15+	Fuschia      = lipgloss.AdaptiveColor{Light: "#EE6FF8", Dark: "#EE6FF8"}
16+	Green        = lipgloss.Color("#04B575")
17+	Red          = lipgloss.AdaptiveColor{Light: "#FF4672", Dark: "#ED567A"}
18+	FaintRed     = lipgloss.AdaptiveColor{Light: "#FF6F91", Dark: "#C74665"}
19 )
20 
21 type Styles struct {
22@@ -44,9 +44,9 @@ type Styles struct {
23 func DefaultStyles() Styles {
24 	s := Styles{}
25 
26-	s.Cursor = lipgloss.NewStyle().Foreground(fuschia)
27+	s.Cursor = lipgloss.NewStyle().Foreground(Fuschia)
28 	s.Wrap = lipgloss.NewStyle().Width(58)
29-	s.Keyword = lipgloss.NewStyle().Foreground(green)
30+	s.Keyword = lipgloss.NewStyle().Foreground(Green)
31 	s.Paragraph = s.Wrap.Copy().Margin(1, 0, 0, 2)
32 	s.Code = lipgloss.NewStyle().
33 		Foreground(lipgloss.AdaptiveColor{Light: "#FF4672", Dark: "#ED567A"}).
34@@ -54,30 +54,30 @@ func DefaultStyles() Styles {
35 		Padding(0, 1)
36 	s.Subtle = lipgloss.NewStyle().
37 		Foreground(lipgloss.AdaptiveColor{Light: "#9B9B9B", Dark: "#5C5C5C"})
38-	s.Error = lipgloss.NewStyle().Foreground(red)
39+	s.Error = lipgloss.NewStyle().Foreground(Red)
40 	s.Prompt = lipgloss.NewStyle().MarginRight(1).SetString(">")
41-	s.FocusedPrompt = s.Prompt.Copy().Foreground(fuschia)
42-	s.Note = lipgloss.NewStyle().Foreground(green)
43+	s.FocusedPrompt = s.Prompt.Copy().Foreground(Fuschia)
44+	s.Note = lipgloss.NewStyle().Foreground(Green)
45 	s.NoteDim = lipgloss.NewStyle().
46 		Foreground(lipgloss.AdaptiveColor{Light: "#ABE5D1", Dark: "#2B4A3F"})
47 	s.Delete = s.Error.Copy()
48-	s.DeleteDim = lipgloss.NewStyle().Foreground(faintRed)
49-	s.Label = lipgloss.NewStyle().Foreground(fuschia)
50-	s.LabelDim = lipgloss.NewStyle().Foreground(indigo)
51-	s.ListKey = lipgloss.NewStyle().Foreground(indigo)
52-	s.ListDim = lipgloss.NewStyle().Foreground(subtleIndigo)
53+	s.DeleteDim = lipgloss.NewStyle().Foreground(FaintRed)
54+	s.Label = lipgloss.NewStyle().Foreground(Fuschia)
55+	s.LabelDim = lipgloss.NewStyle().Foreground(Indigo)
56+	s.ListKey = lipgloss.NewStyle().Foreground(Indigo)
57+	s.ListDim = lipgloss.NewStyle().Foreground(SubtleIndigo)
58 	s.InactivePagination = lipgloss.NewStyle().
59 		Foreground(lipgloss.AdaptiveColor{Light: "#CACACA", Dark: "#4F4F4F"})
60 	s.SelectionMarker = lipgloss.NewStyle().
61-		Foreground(fuschia).
62+		Foreground(Fuschia).
63 		PaddingRight(1).
64 		SetString(">")
65 	s.Checkmark = lipgloss.NewStyle().
66 		SetString("✔").
67-		Foreground(green)
68-	s.SelectedMenuItem = lipgloss.NewStyle().Foreground(fuschia)
69+		Foreground(Green)
70+	s.SelectedMenuItem = lipgloss.NewStyle().Foreground(Fuschia)
71 	s.Logo = lipgloss.NewStyle().
72-		Foreground(cream).
73+		Foreground(Cream).
74 		Background(lipgloss.Color("#5A56E0")).
75 		Padding(0, 1)
76 	s.App = lipgloss.NewStyle().Margin(1, 0, 1, 2)
M wish/cms/ui/common/views.go
+3, -3
 1@@ -35,19 +35,19 @@ func VerticalLine(state State) string {
 2 		String()
 3 }
 4 
 5-var valStyle = lipgloss.NewStyle().Foreground(indigo)
 6+var valStyle = lipgloss.NewStyle().Foreground(Indigo)
 7 
 8 var (
 9 	spinnerStyle = lipgloss.NewStyle().
10 			Foreground(lipgloss.AdaptiveColor{Light: "#8E8E8E", Dark: "#747373"})
11 
12 	blurredButtonStyle = lipgloss.NewStyle().
13-				Foreground(cream).
14+				Foreground(Cream).
15 				Background(lipgloss.AdaptiveColor{Light: "#BDB0BE", Dark: "#827983"}).
16 				Padding(0, 3)
17 
18 	focusedButtonStyle = blurredButtonStyle.Copy().
19-				Background(fuschia)
20+				Background(Fuschia)
21 )
22 
23 // KeyValueView renders key-value pairs.