blob: 88047d8294b0676393f3496ebb5b5ce475b6de5f [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
Davit Tabidze207ce082024-04-09 19:15:25 +040021type AppLauncherInfo struct {
Davit Tabidze563b6ad2024-06-11 13:38:56 +040022 Id string
23 Name string
24 Icon template.HTML
25 Help []HelpDocumentRendered
26 URL string
27 DisplayURL string
Davit Tabidze207ce082024-04-09 19:15:25 +040028}
29
gioc23530e2024-05-01 11:06:09 +040030type HelpDocumentRendered struct {
31 Title string
32 Contents template.HTML
33 Children []HelpDocumentRendered
34}
35
Davit Tabidze207ce082024-04-09 19:15:25 +040036type AppDirectory interface {
37 GetAllApps() ([]AppLauncherInfo, error)
38}
39
Davit Tabidze56f86a42024-04-09 19:15:25 +040040type AppManagerDirectory struct {
41 AppManager *installer.AppManager
42}
43
gio09a3e5b2024-04-26 14:11:06 +040044func (d *AppManagerDirectory) GetAllApps() ([]AppLauncherInfo, error) {
gio7fbd4ad2024-08-27 10:06:39 +040045 all, err := d.AppManager.GetAllInstances()
gio09a3e5b2024-04-26 14:11:06 +040046 if err != nil {
47 return nil, err
48 }
49 ret := []AppLauncherInfo{}
50 for _, a := range all {
51 if a.URL == "" && len(a.Help) == 0 {
52 continue
53 }
54 ret = append(ret, AppLauncherInfo{
Davit Tabidze563b6ad2024-06-11 13:38:56 +040055 Id: a.Id,
56 Name: a.AppId,
57 Icon: template.HTML(a.Icon),
58 Help: toMarkdown(a.Help),
59 URL: a.URL,
60 DisplayURL: shortenURL(a.URL, a.Env.Domain),
gio09a3e5b2024-04-26 14:11:06 +040061 })
62 }
Davit Tabidze96718462024-05-22 14:06:02 +040063 sort.Slice(ret, func(i, j int) bool {
64 if ret[i].Name == "app-manager" {
65 return true
66 }
67 if ret[j].Name == "app-manager" {
68 return false
69 }
70 if ret[i].Name == "headscale" {
71 return ret[j].Name != "app-manager"
72 }
73 if ret[j].Name == "headscale" {
74 return false
75 }
76 return ret[i].Name < ret[j].Name
77 })
gio09a3e5b2024-04-26 14:11:06 +040078 return ret, nil
79}
80
Davit Tabidze207ce082024-04-09 19:15:25 +040081type LauncherServer struct {
82 port int
Davit Tabidze563b6ad2024-06-11 13:38:56 +040083 logoutURL string
Davit Tabidze207ce082024-04-09 19:15:25 +040084 appDirectory AppDirectory
85 homeTmpl *template.Template
86}
87
88func NewLauncherServer(
89 port int,
Davit Tabidze563b6ad2024-06-11 13:38:56 +040090 logoutURL string,
Davit Tabidze207ce082024-04-09 19:15:25 +040091 appDirectory AppDirectory,
92) (*LauncherServer, error) {
93 tmpl, err := indexHTML.ReadFile("launcher-tmpl/launcher.html")
94 if err != nil {
95 return nil, fmt.Errorf("failed to parse template: %v", err)
96 }
giobe7b7242024-05-20 18:13:55 +040097 t := template.New("index").Funcs(template.FuncMap(sprig.FuncMap())).Funcs(template.FuncMap{
Davit Tabidze207ce082024-04-09 19:15:25 +040098 "GetUserInitials": getUserInitials,
99 "CleanAppName": cleanAppName,
100 })
101 t, err = t.Parse(string(tmpl))
102 if err != nil {
103 return nil, fmt.Errorf("failed to parse template: %v", err)
104 }
105 return &LauncherServer{
106 port,
Davit Tabidze563b6ad2024-06-11 13:38:56 +0400107 logoutURL,
Davit Tabidze207ce082024-04-09 19:15:25 +0400108 appDirectory,
109 t,
110 }, nil
111}
112
113func getUserInitials(username string) string {
114 if username == "" {
115 return ""
116 }
117 return strings.ToUpper(username[:1])
118}
119
120func cleanAppName(name string) string {
121 cleanName := strings.ToLower(name)
122 cleanName = strings.ReplaceAll(cleanName, " ", "-")
123 return cleanName
124}
125
Davit Tabidze563b6ad2024-06-11 13:38:56 +0400126func shortenURL(url, domain string) string {
127 return strings.Replace(url, domain, "..", 1)
128}
129
Davit Tabidze56f86a42024-04-09 19:15:25 +0400130func getLoggedInUser(r *http.Request) (string, error) {
131 if user := r.Header.Get("X-User"); user != "" {
132 return user, nil
133 } else {
134 return "", fmt.Errorf("unauthenticated")
135 }
136 // return "Username", nil
137}
138
Davit Tabidze207ce082024-04-09 19:15:25 +0400139func (s *LauncherServer) Start() {
gio1bf00802024-08-17 12:31:41 +0400140 http.Handle("/stat/", cachingHandler{http.FileServer(http.FS(statAssets))})
Davit Tabidze207ce082024-04-09 19:15:25 +0400141 http.HandleFunc("/", s.homeHandler)
142 log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", s.port), nil))
143}
144
145type homeHandlerData struct {
146 LoggedInUsername string
147 AllAppsInfo []AppLauncherInfo
Davit Tabidze563b6ad2024-06-11 13:38:56 +0400148 LogoutURL string
Davit Tabidze207ce082024-04-09 19:15:25 +0400149}
150
151func (s *LauncherServer) homeHandler(w http.ResponseWriter, r *http.Request) {
Davit Tabidze56f86a42024-04-09 19:15:25 +0400152 loggedInUsername, err := getLoggedInUser(r)
153 if err != nil {
154 http.Error(w, "User Not Logged In", http.StatusUnauthorized)
155 return
156 }
Davit Tabidze207ce082024-04-09 19:15:25 +0400157 allAppsInfo, err := s.appDirectory.GetAllApps()
158 if err != nil {
Davit Tabidze56f86a42024-04-09 19:15:25 +0400159 http.Error(w, err.Error(), http.StatusInternalServerError)
Davit Tabidze207ce082024-04-09 19:15:25 +0400160 }
161 data := homeHandlerData{
162 LoggedInUsername: loggedInUsername,
163 AllAppsInfo: allAppsInfo,
Davit Tabidze563b6ad2024-06-11 13:38:56 +0400164 LogoutURL: s.logoutURL,
Davit Tabidze207ce082024-04-09 19:15:25 +0400165 }
166 if err := s.homeTmpl.Execute(w, data); err != nil {
167 http.Error(w, err.Error(), http.StatusInternalServerError)
168 return
169 }
170}
gioc23530e2024-05-01 11:06:09 +0400171
172func toMarkdown(help []installer.HelpDocument) []HelpDocumentRendered {
173 if help == nil {
174 return nil
175 }
176 var ret []HelpDocumentRendered
177 for _, h := range help {
178 ret = append(ret, HelpDocumentRendered{
179 Title: h.Title,
180 Contents: template.HTML(markdown.ToHTML([]byte(h.Contents), nil, nil)),
181 Children: toMarkdown(h.Children),
182 })
183 }
184 return ret
185}