blob: e4483e126b26dd0f482aa23f5e9e6872af291dde [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"
gio5fb00322024-10-26 09:23:03 +040017 "github.com/gomarkdown/markdown/html"
Davit Tabidze207ce082024-04-09 19:15:25 +040018)
19
gio59946282024-10-07 12:55:51 +040020//go:embed templates/launcher.html
Davit Tabidze207ce082024-04-09 19:15:25 +040021var indexHTML embed.FS
22
gio59946282024-10-07 12:55:51 +040023//go:embed static/*
24var staticAssets embed.FS
25
Davit Tabidze207ce082024-04-09 19:15:25 +040026type AppLauncherInfo struct {
Davit Tabidze563b6ad2024-06-11 13:38:56 +040027 Id string
28 Name string
29 Icon template.HTML
30 Help []HelpDocumentRendered
31 URL string
32 DisplayURL string
Davit Tabidze207ce082024-04-09 19:15:25 +040033}
34
gioc23530e2024-05-01 11:06:09 +040035type HelpDocumentRendered struct {
36 Title string
37 Contents template.HTML
38 Children []HelpDocumentRendered
39}
40
Davit Tabidze207ce082024-04-09 19:15:25 +040041type AppDirectory interface {
42 GetAllApps() ([]AppLauncherInfo, error)
43}
44
Davit Tabidze56f86a42024-04-09 19:15:25 +040045type AppManagerDirectory struct {
46 AppManager *installer.AppManager
47}
48
gio09a3e5b2024-04-26 14:11:06 +040049func (d *AppManagerDirectory) GetAllApps() ([]AppLauncherInfo, error) {
gio7fbd4ad2024-08-27 10:06:39 +040050 all, err := d.AppManager.GetAllInstances()
gio09a3e5b2024-04-26 14:11:06 +040051 if err != nil {
52 return nil, err
53 }
54 ret := []AppLauncherInfo{}
gio5fb00322024-10-26 09:23:03 +040055 mdRenderer := html.NewRenderer(html.RendererOptions{
56 Flags: html.CommonFlags | html.HrefTargetBlank,
57 })
gio09a3e5b2024-04-26 14:11:06 +040058 for _, a := range all {
59 if a.URL == "" && len(a.Help) == 0 {
60 continue
61 }
62 ret = append(ret, AppLauncherInfo{
Davit Tabidze563b6ad2024-06-11 13:38:56 +040063 Id: a.Id,
64 Name: a.AppId,
65 Icon: template.HTML(a.Icon),
gio5fb00322024-10-26 09:23:03 +040066 Help: toMarkdown(a.Help, mdRenderer),
Davit Tabidze563b6ad2024-06-11 13:38:56 +040067 URL: a.URL,
68 DisplayURL: shortenURL(a.URL, a.Env.Domain),
gio09a3e5b2024-04-26 14:11:06 +040069 })
70 }
Davit Tabidze96718462024-05-22 14:06:02 +040071 sort.Slice(ret, func(i, j int) bool {
72 if ret[i].Name == "app-manager" {
73 return true
74 }
75 if ret[j].Name == "app-manager" {
76 return false
77 }
78 if ret[i].Name == "headscale" {
79 return ret[j].Name != "app-manager"
80 }
81 if ret[j].Name == "headscale" {
82 return false
83 }
84 return ret[i].Name < ret[j].Name
85 })
gio09a3e5b2024-04-26 14:11:06 +040086 return ret, nil
87}
88
Davit Tabidze207ce082024-04-09 19:15:25 +040089type LauncherServer struct {
90 port int
giodd213152024-09-27 11:26:59 +020091 authBaseAddr string
Davit Tabidze207ce082024-04-09 19:15:25 +040092 appDirectory AppDirectory
93 homeTmpl *template.Template
94}
95
96func NewLauncherServer(
97 port int,
giodd213152024-09-27 11:26:59 +020098 authBaseAddr string,
Davit Tabidze207ce082024-04-09 19:15:25 +040099 appDirectory AppDirectory,
100) (*LauncherServer, error) {
gio59946282024-10-07 12:55:51 +0400101 tmpl, err := indexHTML.ReadFile("templates/launcher.html")
Davit Tabidze207ce082024-04-09 19:15:25 +0400102 if err != nil {
103 return nil, fmt.Errorf("failed to parse template: %v", err)
104 }
giobe7b7242024-05-20 18:13:55 +0400105 t := template.New("index").Funcs(template.FuncMap(sprig.FuncMap())).Funcs(template.FuncMap{
Davit Tabidze207ce082024-04-09 19:15:25 +0400106 "GetUserInitials": getUserInitials,
107 "CleanAppName": cleanAppName,
108 })
109 t, err = t.Parse(string(tmpl))
110 if err != nil {
111 return nil, fmt.Errorf("failed to parse template: %v", err)
112 }
113 return &LauncherServer{
114 port,
giodd213152024-09-27 11:26:59 +0200115 authBaseAddr,
Davit Tabidze207ce082024-04-09 19:15:25 +0400116 appDirectory,
117 t,
118 }, nil
119}
120
121func getUserInitials(username string) string {
122 if username == "" {
123 return ""
124 }
125 return strings.ToUpper(username[:1])
126}
127
128func cleanAppName(name string) string {
129 cleanName := strings.ToLower(name)
130 cleanName = strings.ReplaceAll(cleanName, " ", "-")
131 return cleanName
132}
133
Davit Tabidze563b6ad2024-06-11 13:38:56 +0400134func shortenURL(url, domain string) string {
135 return strings.Replace(url, domain, "..", 1)
136}
137
Davit Tabidze56f86a42024-04-09 19:15:25 +0400138func getLoggedInUser(r *http.Request) (string, error) {
giodd213152024-09-27 11:26:59 +0200139 if user := r.Header.Get("X-Forwarded-User"); user != "" {
Davit Tabidze56f86a42024-04-09 19:15:25 +0400140 return user, nil
141 } else {
142 return "", fmt.Errorf("unauthenticated")
143 }
144 // return "Username", nil
145}
146
Davit Tabidze207ce082024-04-09 19:15:25 +0400147func (s *LauncherServer) Start() {
gio59946282024-10-07 12:55:51 +0400148 http.Handle("/static/", server.NewCachingHandler(http.FileServer(http.FS(staticAssets))))
Davit Tabidze207ce082024-04-09 19:15:25 +0400149 http.HandleFunc("/", s.homeHandler)
150 log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", s.port), nil))
151}
152
153type homeHandlerData struct {
154 LoggedInUsername string
155 AllAppsInfo []AppLauncherInfo
giodd213152024-09-27 11:26:59 +0200156 AuthBaseAddr string
Davit Tabidze207ce082024-04-09 19:15:25 +0400157}
158
159func (s *LauncherServer) homeHandler(w http.ResponseWriter, r *http.Request) {
Davit Tabidze56f86a42024-04-09 19:15:25 +0400160 loggedInUsername, err := getLoggedInUser(r)
161 if err != nil {
162 http.Error(w, "User Not Logged In", http.StatusUnauthorized)
163 return
164 }
Davit Tabidze207ce082024-04-09 19:15:25 +0400165 allAppsInfo, err := s.appDirectory.GetAllApps()
166 if err != nil {
Davit Tabidze56f86a42024-04-09 19:15:25 +0400167 http.Error(w, err.Error(), http.StatusInternalServerError)
Davit Tabidze207ce082024-04-09 19:15:25 +0400168 }
169 data := homeHandlerData{
170 LoggedInUsername: loggedInUsername,
171 AllAppsInfo: allAppsInfo,
giodd213152024-09-27 11:26:59 +0200172 AuthBaseAddr: s.authBaseAddr,
Davit Tabidze207ce082024-04-09 19:15:25 +0400173 }
174 if err := s.homeTmpl.Execute(w, data); err != nil {
175 http.Error(w, err.Error(), http.StatusInternalServerError)
176 return
177 }
178}
gioc23530e2024-05-01 11:06:09 +0400179
gio5fb00322024-10-26 09:23:03 +0400180func toMarkdown(help []installer.HelpDocument, renderer markdown.Renderer) []HelpDocumentRendered {
gioc23530e2024-05-01 11:06:09 +0400181 if help == nil {
182 return nil
183 }
184 var ret []HelpDocumentRendered
185 for _, h := range help {
186 ret = append(ret, HelpDocumentRendered{
187 Title: h.Title,
gio5fb00322024-10-26 09:23:03 +0400188 Contents: template.HTML(markdown.ToHTML([]byte(h.Contents), nil, renderer)),
189 Children: toMarkdown(h.Children, renderer),
gioc23530e2024-05-01 11:06:09 +0400190 })
191 }
192 return ret
193}