Installer: Refactor and give each searver its own directory
Change-Id: I1db2929e7a35b6f92022dec0c6506d68e0297563
diff --git a/core/installer/server/launcher/server.go b/core/installer/server/launcher/server.go
new file mode 100644
index 0000000..f75a86c
--- /dev/null
+++ b/core/installer/server/launcher/server.go
@@ -0,0 +1,189 @@
+package launcher
+
+import (
+ "embed"
+ "fmt"
+ "html/template"
+ "log"
+ "net/http"
+ "sort"
+ "strings"
+
+ "github.com/giolekva/pcloud/core/installer"
+ "github.com/giolekva/pcloud/core/installer/server"
+
+ "github.com/Masterminds/sprig/v3"
+ "github.com/gomarkdown/markdown"
+)
+
+//go:embed templates/launcher.html
+var indexHTML embed.FS
+
+//go:embed static/*
+var staticAssets embed.FS
+
+type AppLauncherInfo struct {
+ Id string
+ Name string
+ Icon template.HTML
+ Help []HelpDocumentRendered
+ URL string
+ DisplayURL string
+}
+
+type HelpDocumentRendered struct {
+ Title string
+ Contents template.HTML
+ Children []HelpDocumentRendered
+}
+
+type AppDirectory interface {
+ GetAllApps() ([]AppLauncherInfo, error)
+}
+
+type AppManagerDirectory struct {
+ AppManager *installer.AppManager
+}
+
+func (d *AppManagerDirectory) GetAllApps() ([]AppLauncherInfo, error) {
+ all, err := d.AppManager.GetAllInstances()
+ if err != nil {
+ return nil, err
+ }
+ ret := []AppLauncherInfo{}
+ for _, a := range all {
+ if a.URL == "" && len(a.Help) == 0 {
+ continue
+ }
+ ret = append(ret, AppLauncherInfo{
+ Id: a.Id,
+ Name: a.AppId,
+ Icon: template.HTML(a.Icon),
+ Help: toMarkdown(a.Help),
+ URL: a.URL,
+ DisplayURL: shortenURL(a.URL, a.Env.Domain),
+ })
+ }
+ sort.Slice(ret, func(i, j int) bool {
+ if ret[i].Name == "app-manager" {
+ return true
+ }
+ if ret[j].Name == "app-manager" {
+ return false
+ }
+ if ret[i].Name == "headscale" {
+ return ret[j].Name != "app-manager"
+ }
+ if ret[j].Name == "headscale" {
+ return false
+ }
+ return ret[i].Name < ret[j].Name
+ })
+ return ret, nil
+}
+
+type LauncherServer struct {
+ port int
+ authBaseAddr string
+ appDirectory AppDirectory
+ homeTmpl *template.Template
+}
+
+func NewLauncherServer(
+ port int,
+ authBaseAddr string,
+ appDirectory AppDirectory,
+) (*LauncherServer, error) {
+ tmpl, err := indexHTML.ReadFile("templates/launcher.html")
+ if err != nil {
+ return nil, fmt.Errorf("failed to parse template: %v", err)
+ }
+ t := template.New("index").Funcs(template.FuncMap(sprig.FuncMap())).Funcs(template.FuncMap{
+ "GetUserInitials": getUserInitials,
+ "CleanAppName": cleanAppName,
+ })
+ t, err = t.Parse(string(tmpl))
+ if err != nil {
+ return nil, fmt.Errorf("failed to parse template: %v", err)
+ }
+ return &LauncherServer{
+ port,
+ authBaseAddr,
+ appDirectory,
+ t,
+ }, nil
+}
+
+func getUserInitials(username string) string {
+ if username == "" {
+ return ""
+ }
+ return strings.ToUpper(username[:1])
+}
+
+func cleanAppName(name string) string {
+ cleanName := strings.ToLower(name)
+ cleanName = strings.ReplaceAll(cleanName, " ", "-")
+ return cleanName
+}
+
+func shortenURL(url, domain string) string {
+ return strings.Replace(url, domain, "..", 1)
+}
+
+func getLoggedInUser(r *http.Request) (string, error) {
+ if user := r.Header.Get("X-Forwarded-User"); user != "" {
+ return user, nil
+ } else {
+ return "", fmt.Errorf("unauthenticated")
+ }
+ // return "Username", nil
+}
+
+func (s *LauncherServer) Start() {
+ http.Handle("/static/", server.NewCachingHandler(http.FileServer(http.FS(staticAssets))))
+ http.HandleFunc("/", s.homeHandler)
+ log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", s.port), nil))
+}
+
+type homeHandlerData struct {
+ LoggedInUsername string
+ AllAppsInfo []AppLauncherInfo
+ AuthBaseAddr string
+}
+
+func (s *LauncherServer) homeHandler(w http.ResponseWriter, r *http.Request) {
+ loggedInUsername, err := getLoggedInUser(r)
+ if err != nil {
+ http.Error(w, "User Not Logged In", http.StatusUnauthorized)
+ return
+ }
+ allAppsInfo, err := s.appDirectory.GetAllApps()
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ }
+ data := homeHandlerData{
+ LoggedInUsername: loggedInUsername,
+ AllAppsInfo: allAppsInfo,
+ AuthBaseAddr: s.authBaseAddr,
+ }
+ if err := s.homeTmpl.Execute(w, data); err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+}
+
+func toMarkdown(help []installer.HelpDocument) []HelpDocumentRendered {
+ if help == nil {
+ return nil
+ }
+ var ret []HelpDocumentRendered
+ for _, h := range help {
+ ret = append(ret, HelpDocumentRendered{
+ Title: h.Title,
+ Contents: template.HTML(markdown.ToHTML([]byte(h.Contents), nil, nil)),
+ Children: toMarkdown(h.Children),
+ })
+ }
+ return ret
+}