blob: 3dd70b9885722a92fc9a149691240792fa12d614 [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 Tabidze563b6ad2024-06-11 13:38:56 +040025 Id string
26 Name string
27 Icon template.HTML
28 Help []HelpDocumentRendered
29 URL string
30 DisplayURL string
Davit Tabidze207ce082024-04-09 19:15:25 +040031}
32
gioc23530e2024-05-01 11:06:09 +040033type HelpDocumentRendered struct {
34 Title string
35 Contents template.HTML
36 Children []HelpDocumentRendered
37}
38
Davit Tabidze207ce082024-04-09 19:15:25 +040039type AppDirectory interface {
40 GetAllApps() ([]AppLauncherInfo, error)
41}
42
Davit Tabidze56f86a42024-04-09 19:15:25 +040043type AppManagerDirectory struct {
44 AppManager *installer.AppManager
45}
46
gio09a3e5b2024-04-26 14:11:06 +040047func (d *AppManagerDirectory) GetAllApps() ([]AppLauncherInfo, error) {
48 all, err := d.AppManager.FindAllInstances()
49 if err != nil {
50 return nil, err
51 }
52 ret := []AppLauncherInfo{}
53 for _, a := range all {
54 if a.URL == "" && len(a.Help) == 0 {
55 continue
56 }
57 ret = append(ret, AppLauncherInfo{
Davit Tabidze563b6ad2024-06-11 13:38:56 +040058 Id: a.Id,
59 Name: a.AppId,
60 Icon: template.HTML(a.Icon),
61 Help: toMarkdown(a.Help),
62 URL: a.URL,
63 DisplayURL: shortenURL(a.URL, a.Env.Domain),
gio09a3e5b2024-04-26 14:11:06 +040064 })
65 }
Davit Tabidze96718462024-05-22 14:06:02 +040066 sort.Slice(ret, func(i, j int) bool {
67 if ret[i].Name == "app-manager" {
68 return true
69 }
70 if ret[j].Name == "app-manager" {
71 return false
72 }
73 if ret[i].Name == "headscale" {
74 return ret[j].Name != "app-manager"
75 }
76 if ret[j].Name == "headscale" {
77 return false
78 }
79 return ret[i].Name < ret[j].Name
80 })
gio09a3e5b2024-04-26 14:11:06 +040081 return ret, nil
82}
83
Davit Tabidze207ce082024-04-09 19:15:25 +040084type LauncherServer struct {
85 port int
Davit Tabidze563b6ad2024-06-11 13:38:56 +040086 logoutURL string
Davit Tabidze207ce082024-04-09 19:15:25 +040087 appDirectory AppDirectory
88 homeTmpl *template.Template
89}
90
91func NewLauncherServer(
92 port int,
Davit Tabidze563b6ad2024-06-11 13:38:56 +040093 logoutURL string,
Davit Tabidze207ce082024-04-09 19:15:25 +040094 appDirectory AppDirectory,
95) (*LauncherServer, error) {
96 tmpl, err := indexHTML.ReadFile("launcher-tmpl/launcher.html")
97 if err != nil {
98 return nil, fmt.Errorf("failed to parse template: %v", err)
99 }
giobe7b7242024-05-20 18:13:55 +0400100 t := template.New("index").Funcs(template.FuncMap(sprig.FuncMap())).Funcs(template.FuncMap{
Davit Tabidze207ce082024-04-09 19:15:25 +0400101 "GetUserInitials": getUserInitials,
102 "CleanAppName": cleanAppName,
103 })
104 t, err = t.Parse(string(tmpl))
105 if err != nil {
106 return nil, fmt.Errorf("failed to parse template: %v", err)
107 }
108 return &LauncherServer{
109 port,
Davit Tabidze563b6ad2024-06-11 13:38:56 +0400110 logoutURL,
Davit Tabidze207ce082024-04-09 19:15:25 +0400111 appDirectory,
112 t,
113 }, nil
114}
115
116func getUserInitials(username string) string {
117 if username == "" {
118 return ""
119 }
120 return strings.ToUpper(username[:1])
121}
122
123func cleanAppName(name string) string {
124 cleanName := strings.ToLower(name)
125 cleanName = strings.ReplaceAll(cleanName, " ", "-")
126 return cleanName
127}
128
Davit Tabidze563b6ad2024-06-11 13:38:56 +0400129func shortenURL(url, domain string) string {
130 return strings.Replace(url, domain, "..", 1)
131}
132
Davit Tabidze56f86a42024-04-09 19:15:25 +0400133func getLoggedInUser(r *http.Request) (string, error) {
134 if user := r.Header.Get("X-User"); user != "" {
135 return user, nil
136 } else {
137 return "", fmt.Errorf("unauthenticated")
138 }
139 // return "Username", nil
140}
141
Davit Tabidze207ce082024-04-09 19:15:25 +0400142func (s *LauncherServer) Start() {
gio09f8efa2024-06-10 22:35:24 +0400143 http.Handle("/static/", cachingHandler{http.FileServer(http.FS(files))})
Davit Tabidze207ce082024-04-09 19:15:25 +0400144 http.HandleFunc("/", s.homeHandler)
145 log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", s.port), nil))
146}
147
148type homeHandlerData struct {
149 LoggedInUsername string
150 AllAppsInfo []AppLauncherInfo
Davit Tabidze563b6ad2024-06-11 13:38:56 +0400151 LogoutURL string
Davit Tabidze207ce082024-04-09 19:15:25 +0400152}
153
154func (s *LauncherServer) homeHandler(w http.ResponseWriter, r *http.Request) {
Davit Tabidze56f86a42024-04-09 19:15:25 +0400155 loggedInUsername, err := getLoggedInUser(r)
156 if err != nil {
157 http.Error(w, "User Not Logged In", http.StatusUnauthorized)
158 return
159 }
Davit Tabidze207ce082024-04-09 19:15:25 +0400160 allAppsInfo, err := s.appDirectory.GetAllApps()
161 if err != nil {
Davit Tabidze56f86a42024-04-09 19:15:25 +0400162 http.Error(w, err.Error(), http.StatusInternalServerError)
Davit Tabidze207ce082024-04-09 19:15:25 +0400163 }
164 data := homeHandlerData{
165 LoggedInUsername: loggedInUsername,
166 AllAppsInfo: allAppsInfo,
Davit Tabidze563b6ad2024-06-11 13:38:56 +0400167 LogoutURL: s.logoutURL,
Davit Tabidze207ce082024-04-09 19:15:25 +0400168 }
169 if err := s.homeTmpl.Execute(w, data); err != nil {
170 http.Error(w, err.Error(), http.StatusInternalServerError)
171 return
172 }
173}
gioc23530e2024-05-01 11:06:09 +0400174
175func toMarkdown(help []installer.HelpDocument) []HelpDocumentRendered {
176 if help == nil {
177 return nil
178 }
179 var ret []HelpDocumentRendered
180 for _, h := range help {
181 ret = append(ret, HelpDocumentRendered{
182 Title: h.Title,
183 Contents: template.HTML(markdown.ToHTML([]byte(h.Contents), nil, nil)),
184 Children: toMarkdown(h.Children),
185 })
186 }
187 return ret
188}