blob: f75a86c0b57f0b2e90f6e72a1d602ae1f3489f0f [file] [log] [blame]
gio59946282024-10-07 12:55:51 +04001package launcher
Davit Tabidze207ce082024-04-09 19:15:25 +04002
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"
gio59946282024-10-07 12:55:51 +040013 "github.com/giolekva/pcloud/core/installer/server"
gioc23530e2024-05-01 11:06:09 +040014
giobe7b7242024-05-20 18:13:55 +040015 "github.com/Masterminds/sprig/v3"
gioc23530e2024-05-01 11:06:09 +040016 "github.com/gomarkdown/markdown"
Davit Tabidze207ce082024-04-09 19:15:25 +040017)
18
gio59946282024-10-07 12:55:51 +040019//go:embed templates/launcher.html
Davit Tabidze207ce082024-04-09 19:15:25 +040020var indexHTML embed.FS
21
gio59946282024-10-07 12:55:51 +040022//go:embed static/*
23var staticAssets embed.FS
24
Davit Tabidze207ce082024-04-09 19:15:25 +040025type AppLauncherInfo struct {
Davit Tabidze563b6ad2024-06-11 13:38:56 +040026 Id string
27 Name string
28 Icon template.HTML
29 Help []HelpDocumentRendered
30 URL string
31 DisplayURL string
Davit Tabidze207ce082024-04-09 19:15:25 +040032}
33
gioc23530e2024-05-01 11:06:09 +040034type HelpDocumentRendered struct {
35 Title string
36 Contents template.HTML
37 Children []HelpDocumentRendered
38}
39
Davit Tabidze207ce082024-04-09 19:15:25 +040040type AppDirectory interface {
41 GetAllApps() ([]AppLauncherInfo, error)
42}
43
Davit Tabidze56f86a42024-04-09 19:15:25 +040044type AppManagerDirectory struct {
45 AppManager *installer.AppManager
46}
47
gio09a3e5b2024-04-26 14:11:06 +040048func (d *AppManagerDirectory) GetAllApps() ([]AppLauncherInfo, error) {
gio7fbd4ad2024-08-27 10:06:39 +040049 all, err := d.AppManager.GetAllInstances()
gio09a3e5b2024-04-26 14:11:06 +040050 if err != nil {
51 return nil, err
52 }
53 ret := []AppLauncherInfo{}
54 for _, a := range all {
55 if a.URL == "" && len(a.Help) == 0 {
56 continue
57 }
58 ret = append(ret, AppLauncherInfo{
Davit Tabidze563b6ad2024-06-11 13:38:56 +040059 Id: a.Id,
60 Name: a.AppId,
61 Icon: template.HTML(a.Icon),
62 Help: toMarkdown(a.Help),
63 URL: a.URL,
64 DisplayURL: shortenURL(a.URL, a.Env.Domain),
gio09a3e5b2024-04-26 14:11:06 +040065 })
66 }
Davit Tabidze96718462024-05-22 14:06:02 +040067 sort.Slice(ret, func(i, j int) bool {
68 if ret[i].Name == "app-manager" {
69 return true
70 }
71 if ret[j].Name == "app-manager" {
72 return false
73 }
74 if ret[i].Name == "headscale" {
75 return ret[j].Name != "app-manager"
76 }
77 if ret[j].Name == "headscale" {
78 return false
79 }
80 return ret[i].Name < ret[j].Name
81 })
gio09a3e5b2024-04-26 14:11:06 +040082 return ret, nil
83}
84
Davit Tabidze207ce082024-04-09 19:15:25 +040085type LauncherServer struct {
86 port int
giodd213152024-09-27 11:26:59 +020087 authBaseAddr string
Davit Tabidze207ce082024-04-09 19:15:25 +040088 appDirectory AppDirectory
89 homeTmpl *template.Template
90}
91
92func NewLauncherServer(
93 port int,
giodd213152024-09-27 11:26:59 +020094 authBaseAddr string,
Davit Tabidze207ce082024-04-09 19:15:25 +040095 appDirectory AppDirectory,
96) (*LauncherServer, error) {
gio59946282024-10-07 12:55:51 +040097 tmpl, err := indexHTML.ReadFile("templates/launcher.html")
Davit Tabidze207ce082024-04-09 19:15:25 +040098 if err != nil {
99 return nil, fmt.Errorf("failed to parse template: %v", err)
100 }
giobe7b7242024-05-20 18:13:55 +0400101 t := template.New("index").Funcs(template.FuncMap(sprig.FuncMap())).Funcs(template.FuncMap{
Davit Tabidze207ce082024-04-09 19:15:25 +0400102 "GetUserInitials": getUserInitials,
103 "CleanAppName": cleanAppName,
104 })
105 t, err = t.Parse(string(tmpl))
106 if err != nil {
107 return nil, fmt.Errorf("failed to parse template: %v", err)
108 }
109 return &LauncherServer{
110 port,
giodd213152024-09-27 11:26:59 +0200111 authBaseAddr,
Davit Tabidze207ce082024-04-09 19:15:25 +0400112 appDirectory,
113 t,
114 }, nil
115}
116
117func getUserInitials(username string) string {
118 if username == "" {
119 return ""
120 }
121 return strings.ToUpper(username[:1])
122}
123
124func cleanAppName(name string) string {
125 cleanName := strings.ToLower(name)
126 cleanName = strings.ReplaceAll(cleanName, " ", "-")
127 return cleanName
128}
129
Davit Tabidze563b6ad2024-06-11 13:38:56 +0400130func shortenURL(url, domain string) string {
131 return strings.Replace(url, domain, "..", 1)
132}
133
Davit Tabidze56f86a42024-04-09 19:15:25 +0400134func getLoggedInUser(r *http.Request) (string, error) {
giodd213152024-09-27 11:26:59 +0200135 if user := r.Header.Get("X-Forwarded-User"); user != "" {
Davit Tabidze56f86a42024-04-09 19:15:25 +0400136 return user, nil
137 } else {
138 return "", fmt.Errorf("unauthenticated")
139 }
140 // return "Username", nil
141}
142
Davit Tabidze207ce082024-04-09 19:15:25 +0400143func (s *LauncherServer) Start() {
gio59946282024-10-07 12:55:51 +0400144 http.Handle("/static/", server.NewCachingHandler(http.FileServer(http.FS(staticAssets))))
Davit Tabidze207ce082024-04-09 19:15:25 +0400145 http.HandleFunc("/", s.homeHandler)
146 log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", s.port), nil))
147}
148
149type homeHandlerData struct {
150 LoggedInUsername string
151 AllAppsInfo []AppLauncherInfo
giodd213152024-09-27 11:26:59 +0200152 AuthBaseAddr string
Davit Tabidze207ce082024-04-09 19:15:25 +0400153}
154
155func (s *LauncherServer) homeHandler(w http.ResponseWriter, r *http.Request) {
Davit Tabidze56f86a42024-04-09 19:15:25 +0400156 loggedInUsername, err := getLoggedInUser(r)
157 if err != nil {
158 http.Error(w, "User Not Logged In", http.StatusUnauthorized)
159 return
160 }
Davit Tabidze207ce082024-04-09 19:15:25 +0400161 allAppsInfo, err := s.appDirectory.GetAllApps()
162 if err != nil {
Davit Tabidze56f86a42024-04-09 19:15:25 +0400163 http.Error(w, err.Error(), http.StatusInternalServerError)
Davit Tabidze207ce082024-04-09 19:15:25 +0400164 }
165 data := homeHandlerData{
166 LoggedInUsername: loggedInUsername,
167 AllAppsInfo: allAppsInfo,
giodd213152024-09-27 11:26:59 +0200168 AuthBaseAddr: s.authBaseAddr,
Davit Tabidze207ce082024-04-09 19:15:25 +0400169 }
170 if err := s.homeTmpl.Execute(w, data); err != nil {
171 http.Error(w, err.Error(), http.StatusInternalServerError)
172 return
173 }
174}
gioc23530e2024-05-01 11:06:09 +0400175
176func toMarkdown(help []installer.HelpDocument) []HelpDocumentRendered {
177 if help == nil {
178 return nil
179 }
180 var ret []HelpDocumentRendered
181 for _, h := range help {
182 ret = append(ret, HelpDocumentRendered{
183 Title: h.Title,
184 Contents: template.HTML(markdown.ToHTML([]byte(h.Contents), nil, nil)),
185 Children: toMarkdown(h.Children),
186 })
187 }
188 return ret
189}