blob: 9d2b2f4775de7db3dd2703c770303c175a2d9def [file] [log] [blame]
Davit Tabidze207ce082024-04-09 19:15:25 +04001package welcome
2
3import (
4 "embed"
5 "fmt"
6 "html/template"
7 "log"
8 "net/http"
Davit Tabidze96718462024-05-22 14:06:02 +04009 "sort"
Davit Tabidze207ce082024-04-09 19:15:25 +040010 "strings"
Davit Tabidze56f86a42024-04-09 19:15:25 +040011
12 "github.com/giolekva/pcloud/core/installer"
gioc23530e2024-05-01 11:06:09 +040013
giobe7b7242024-05-20 18:13:55 +040014 "github.com/Masterminds/sprig/v3"
gioc23530e2024-05-01 11:06:09 +040015 "github.com/gomarkdown/markdown"
Davit Tabidze207ce082024-04-09 19:15:25 +040016)
17
18//go:embed launcher-tmpl/launcher.html
19var indexHTML embed.FS
20
21//go:embed static/*
22var files embed.FS
23
24type AppLauncherInfo struct {
Davit Tabidze56f86a42024-04-09 19:15:25 +040025 Name string
26 Icon template.HTML
gioc23530e2024-05-01 11:06:09 +040027 Help []HelpDocumentRendered
Davit Tabidze56f86a42024-04-09 19:15:25 +040028 Url string
Davit Tabidze207ce082024-04-09 19:15:25 +040029}
30
gioc23530e2024-05-01 11:06:09 +040031type HelpDocumentRendered struct {
32 Title string
33 Contents template.HTML
34 Children []HelpDocumentRendered
35}
36
Davit Tabidze207ce082024-04-09 19:15:25 +040037type AppDirectory interface {
38 GetAllApps() ([]AppLauncherInfo, error)
39}
40
Davit Tabidze56f86a42024-04-09 19:15:25 +040041type AppManagerDirectory struct {
42 AppManager *installer.AppManager
43}
44
gio09a3e5b2024-04-26 14:11:06 +040045func (d *AppManagerDirectory) GetAllApps() ([]AppLauncherInfo, error) {
46 all, err := d.AppManager.FindAllInstances()
47 if err != nil {
48 return nil, err
49 }
50 ret := []AppLauncherInfo{}
51 for _, a := range all {
52 if a.URL == "" && len(a.Help) == 0 {
53 continue
54 }
55 ret = append(ret, AppLauncherInfo{
56 Name: a.AppId,
57 Icon: template.HTML(a.Icon),
gioc23530e2024-05-01 11:06:09 +040058 Help: toMarkdown(a.Help),
gio09a3e5b2024-04-26 14:11:06 +040059 Url: a.URL,
60 })
61 }
Davit Tabidze96718462024-05-22 14:06:02 +040062 sort.Slice(ret, func(i, j int) bool {
63 if ret[i].Name == "app-manager" {
64 return true
65 }
66 if ret[j].Name == "app-manager" {
67 return false
68 }
69 if ret[i].Name == "headscale" {
70 return ret[j].Name != "app-manager"
71 }
72 if ret[j].Name == "headscale" {
73 return false
74 }
75 return ret[i].Name < ret[j].Name
76 })
gio09a3e5b2024-04-26 14:11:06 +040077 return ret, nil
78}
79
Davit Tabidze207ce082024-04-09 19:15:25 +040080type LauncherServer struct {
81 port int
82 logoutUrl string
83 appDirectory AppDirectory
84 homeTmpl *template.Template
85}
86
87func NewLauncherServer(
88 port int,
89 logoutUrl string,
90 appDirectory AppDirectory,
91) (*LauncherServer, error) {
92 tmpl, err := indexHTML.ReadFile("launcher-tmpl/launcher.html")
93 if err != nil {
94 return nil, fmt.Errorf("failed to parse template: %v", err)
95 }
giobe7b7242024-05-20 18:13:55 +040096 t := template.New("index").Funcs(template.FuncMap(sprig.FuncMap())).Funcs(template.FuncMap{
Davit Tabidze207ce082024-04-09 19:15:25 +040097 "GetUserInitials": getUserInitials,
98 "CleanAppName": cleanAppName,
99 })
100 t, err = t.Parse(string(tmpl))
101 if err != nil {
102 return nil, fmt.Errorf("failed to parse template: %v", err)
103 }
104 return &LauncherServer{
105 port,
106 logoutUrl,
107 appDirectory,
108 t,
109 }, nil
110}
111
112func getUserInitials(username string) string {
113 if username == "" {
114 return ""
115 }
116 return strings.ToUpper(username[:1])
117}
118
119func cleanAppName(name string) string {
120 cleanName := strings.ToLower(name)
121 cleanName = strings.ReplaceAll(cleanName, " ", "-")
122 return cleanName
123}
124
Davit Tabidze56f86a42024-04-09 19:15:25 +0400125func getLoggedInUser(r *http.Request) (string, error) {
126 if user := r.Header.Get("X-User"); user != "" {
127 return user, nil
128 } else {
129 return "", fmt.Errorf("unauthenticated")
130 }
131 // return "Username", nil
132}
133
Davit Tabidze207ce082024-04-09 19:15:25 +0400134func (s *LauncherServer) Start() {
135 http.Handle("/static/", http.FileServer(http.FS(files)))
136 http.HandleFunc("/", s.homeHandler)
137 log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", s.port), nil))
138}
139
140type homeHandlerData struct {
141 LoggedInUsername string
142 AllAppsInfo []AppLauncherInfo
143}
144
145func (s *LauncherServer) homeHandler(w http.ResponseWriter, r *http.Request) {
Davit Tabidze56f86a42024-04-09 19:15:25 +0400146 loggedInUsername, err := getLoggedInUser(r)
147 if err != nil {
148 http.Error(w, "User Not Logged In", http.StatusUnauthorized)
149 return
150 }
Davit Tabidze207ce082024-04-09 19:15:25 +0400151 allAppsInfo, err := s.appDirectory.GetAllApps()
152 if err != nil {
Davit Tabidze56f86a42024-04-09 19:15:25 +0400153 http.Error(w, err.Error(), http.StatusInternalServerError)
Davit Tabidze207ce082024-04-09 19:15:25 +0400154 }
155 data := homeHandlerData{
156 LoggedInUsername: loggedInUsername,
157 AllAppsInfo: allAppsInfo,
158 }
159 if err := s.homeTmpl.Execute(w, data); err != nil {
160 http.Error(w, err.Error(), http.StatusInternalServerError)
161 return
162 }
163}
gioc23530e2024-05-01 11:06:09 +0400164
165func toMarkdown(help []installer.HelpDocument) []HelpDocumentRendered {
166 if help == nil {
167 return nil
168 }
169 var ret []HelpDocumentRendered
170 for _, h := range help {
171 ret = append(ret, HelpDocumentRendered{
172 Title: h.Title,
173 Contents: template.HTML(markdown.ToHTML([]byte(h.Contents), nil, nil)),
174 Children: toMarkdown(h.Children),
175 })
176 }
177 return ret
178}