blob: 033e1dbca9f9913b423d0b098efd3fe01c7a4074 [file] [log] [blame]
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +04001package welcome
2
3import (
4 "bytes"
5 "embed"
6 "encoding/json"
7 "fmt"
8 "html/template"
9 "io/ioutil"
10 "log"
11 "net/http"
12 // "net/http/httputil"
13 // "net/url"
14
15 "github.com/Masterminds/sprig/v3"
16 "github.com/labstack/echo/v4"
17
18 "github.com/giolekva/pcloud/core/installer"
19)
20
21//go:embed appmanager-tmpl
22var mgrTmpl embed.FS
23
24//go:embed appmanager-tmpl/base.html
25var baseHtmlTmpl string
26
27//go:embed appmanager-tmpl/app.html
28var appHtmlTmpl string
29
30type AppManagerServer struct {
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +040031 port int
32 m *installer.AppManager
33 r installer.AppRepository[installer.StoreApp]
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040034}
35
36func NewAppManagerServer(
37 port int,
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040038 m *installer.AppManager,
39 r installer.AppRepository[installer.StoreApp],
40) *AppManagerServer {
41 return &AppManagerServer{
42 port,
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040043 m,
44 r,
45 }
46}
47
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +040048func (s *AppManagerServer) Start() error {
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040049 e := echo.New()
50 e.StaticFS("/static", echo.MustSubFS(staticAssets, "static"))
51 e.GET("/api/app-repo", s.handleAppRepo)
52 e.POST("/api/app/:slug/render", s.handleAppRender)
53 e.POST("/api/app/:slug/install", s.handleAppInstall)
54 e.GET("/api/app/:slug", s.handleApp)
55 e.GET("/api/instance/:slug", s.handleInstance)
56 e.POST("/api/instance/:slug/update", s.handleAppUpdate)
57 e.POST("/api/instance/:slug/remove", s.handleAppRemove)
58 e.GET("/", s.handleIndex)
59 e.GET("/app/:slug", s.handleAppUI)
60 e.GET("/instance/:slug", s.handleInstanceUI)
61 fmt.Printf("Starting HTTP server on port: %d\n", s.port)
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +040062 return e.Start(fmt.Sprintf(":%d", s.port))
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040063}
64
65type app struct {
66 Name string `json:"name"`
67 Icon template.HTML `json:"icon"`
68 ShortDescription string `json:"shortDescription"`
69 Slug string `json:"slug"`
70 Schema string `json:"schema"`
71 Instances []installer.AppConfig `json:"instances,omitempty"`
72}
73
74func (s *AppManagerServer) handleAppRepo(c echo.Context) error {
75 all, err := s.r.GetAll()
76 if err != nil {
77 return err
78 }
79 resp := make([]app, len(all))
80 for i, a := range all {
81 resp[i] = app{a.Name, a.Icon, a.ShortDescription, a.Name, a.Schema, nil}
82 }
83 return c.JSON(http.StatusOK, resp)
84}
85
86func (s *AppManagerServer) handleApp(c echo.Context) error {
87 slug := c.Param("slug")
88 a, err := s.r.Find(slug)
89 if err != nil {
90 return err
91 }
92 instances, err := s.m.FindAllInstances(slug)
93 if err != nil {
94 return err
95 }
96 for _, instance := range instances {
97 values, ok := instance.Config["Values"].(map[string]any)
98 if !ok {
99 return fmt.Errorf("Expected map")
100 }
101 for k, v := range values {
102 if k == "Network" {
103 n, ok := v.(map[string]any)
104 if !ok {
105 return fmt.Errorf("Expected map")
106 }
107 values["Network"], ok = n["Name"]
108 if !ok {
109 return fmt.Errorf("Missing Name")
110 }
111 break
112 }
113 }
114 }
115 return c.JSON(http.StatusOK, app{a.Name, a.Icon, a.ShortDescription, a.Name, a.Schema, instances})
116}
117
118func (s *AppManagerServer) handleInstance(c echo.Context) error {
119 slug := c.Param("slug")
120 instance, err := s.m.FindInstance(slug)
121 if err != nil {
122 return err
123 }
124 values, ok := instance.Config["Values"].(map[string]any)
125 if !ok {
126 return fmt.Errorf("Expected map")
127 }
128 for k, v := range values {
129 if k == "Network" {
130 n, ok := v.(map[string]any)
131 if !ok {
132 return fmt.Errorf("Expected map")
133 }
134 values["Network"], ok = n["Name"]
135 if !ok {
136 return fmt.Errorf("Missing Name")
137 }
138 break
139 }
140 }
141 a, err := s.r.Find(instance.AppId)
142 if err != nil {
143 return err
144 }
145 return c.JSON(http.StatusOK, app{a.Name, a.Icon, a.ShortDescription, a.Name, a.Schema, []installer.AppConfig{instance}})
146}
147
148type file struct {
149 Name string `json:"name"`
150 Contents string `json:"contents"`
151}
152
153type rendered struct {
154 Readme string `json:"readme"`
155 Files []file `json:"files"`
156}
157
158func (s *AppManagerServer) handleAppRender(c echo.Context) error {
159 slug := c.Param("slug")
160 contents, err := ioutil.ReadAll(c.Request().Body)
161 if err != nil {
162 return err
163 }
164 global, err := s.m.Config()
165 if err != nil {
166 return err
167 }
168 var values map[string]any
169 if err := json.Unmarshal(contents, &values); err != nil {
170 return err
171 }
172 if network, ok := values["Network"]; ok {
173 for _, n := range installer.CreateNetworks(global) {
174 if n.Name == network { // TODO(giolekva): handle not found
175 values["Network"] = n
176 }
177 }
178 }
179 all := map[string]any{
180 "Global": global.Values,
181 "Values": values,
182 }
183 a, err := s.r.Find(slug)
184 if err != nil {
185 return err
186 }
187 var readme bytes.Buffer
188 if err := a.Readme.Execute(&readme, all); err != nil {
189 return err
190 }
191 var resp rendered
192 resp.Readme = readme.String()
193 for _, tmpl := range a.Templates { // TODO(giolekva): deduplicate with Install
194 var f bytes.Buffer
195 if err := tmpl.Execute(&f, all); err != nil {
196 fmt.Printf("%+v\n", all)
197 fmt.Println(err.Error())
198 return err
199 } else {
200 resp.Files = append(resp.Files, file{tmpl.Name(), f.String()})
201 }
202 }
203 out, err := json.Marshal(resp)
204 if err != nil {
205 return err
206 }
207 if _, err := c.Response().Writer.Write(out); err != nil {
208 return err
209 }
210 return nil
211}
212
213func (s *AppManagerServer) handleAppInstall(c echo.Context) error {
214 slug := c.Param("slug")
215 contents, err := ioutil.ReadAll(c.Request().Body)
216 if err != nil {
217 return err
218 }
219 var values map[string]any
220 if err := json.Unmarshal(contents, &values); err != nil {
221 return err
222 }
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400223 log.Printf("Values: %+v\n", values)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400224 a, err := s.r.Find(slug)
225 if err != nil {
226 return err
227 }
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400228 log.Printf("Found application: %s\n", slug)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400229 config, err := s.m.Config()
230 if err != nil {
231 return err
232 }
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400233 log.Printf("Configuration: %+v\n", config)
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400234 nsGen := installer.NewPrefixGenerator(config.Values.NamespacePrefix)
235 suffixGen := installer.NewFixedLengthRandomSuffixGenerator(3)
236 if err := s.m.Install(a.App, nsGen, suffixGen, values); err != nil {
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400237 log.Printf("%s\n", err.Error())
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400238 return err
239 }
240 return c.String(http.StatusOK, "Installed")
241}
242
243func (s *AppManagerServer) handleAppUpdate(c echo.Context) error {
244 slug := c.Param("slug")
245 appConfig, err := s.m.AppConfig(slug)
246 if err != nil {
247 return err
248 }
249 contents, err := ioutil.ReadAll(c.Request().Body)
250 if err != nil {
251 return err
252 }
253 var values map[string]any
254 if err := json.Unmarshal(contents, &values); err != nil {
255 return err
256 }
257 a, err := s.r.Find(appConfig.AppId)
258 if err != nil {
259 return err
260 }
261 if err := s.m.Update(a.App, slug, values); err != nil {
262 return err
263 }
264 return c.String(http.StatusOK, "Installed")
265}
266
267func (s *AppManagerServer) handleAppRemove(c echo.Context) error {
268 slug := c.Param("slug")
269 if err := s.m.Remove(slug); err != nil {
270 return err
271 }
272 return c.String(http.StatusOK, "Installed")
273}
274
275func (s *AppManagerServer) handleIndex(c echo.Context) error {
276 tmpl, err := template.ParseFS(mgrTmpl, "appmanager-tmpl/base.html", "appmanager-tmpl/index.html")
277 if err != nil {
278 return err
279 }
280 all, err := s.r.GetAll()
281 if err != nil {
282 return err
283 }
284 resp := make([]app, len(all))
285 for i, a := range all {
286 resp[i] = app{a.Name, a.Icon, a.ShortDescription, a.Name, a.Schema, nil}
287 }
288 return tmpl.Execute(c.Response(), resp)
289}
290
291type appContext[T any] struct {
292 App *T
293 Instance *installer.AppConfig
294 Instances []installer.AppConfig
295 AvailableNetworks []installer.Network
296}
297
298func (s *AppManagerServer) handleAppUI(c echo.Context) error {
299 baseTmpl, err := newTemplate().Parse(baseHtmlTmpl)
300 if err != nil {
301 return err
302 }
303 appTmpl, err := template.Must(baseTmpl.Clone()).Parse(appHtmlTmpl)
304 if err != nil {
305 fmt.Println(err)
306 return err
307 }
308 global, err := s.m.Config()
309 if err != nil {
310 return err
311 }
312 slug := c.Param("slug")
313 a, err := s.r.Find(slug)
314 if err != nil {
315 return err
316 }
317 instances, err := s.m.FindAllInstances(slug)
318 if err != nil {
319 return err
320 }
321 err = appTmpl.Execute(c.Response(), appContext[installer.StoreApp]{
322 App: a,
323 Instances: instances,
324 AvailableNetworks: installer.CreateNetworks(global),
325 })
326 fmt.Println(err)
327 return err
328}
329
330func (s *AppManagerServer) handleInstanceUI(c echo.Context) error {
331 baseTmpl, err := newTemplate().Parse(baseHtmlTmpl)
332 if err != nil {
333 return err
334 }
335 appTmpl, err := template.Must(baseTmpl.Clone()).Parse(appHtmlTmpl)
336 // tmpl, err := newTemplate().ParseFS(mgrTmpl, "appmanager-tmpl/base.html", "appmanager-tmpl/app.html")
337 if err != nil {
338 fmt.Println(err)
339 return err
340 }
341 global, err := s.m.Config()
342 if err != nil {
343 return err
344 }
345 slug := c.Param("slug")
346 instance, err := s.m.FindInstance(slug)
347 if err != nil {
348 return err
349 }
350 a, err := s.r.Find(instance.AppId)
351 if err != nil {
352 return err
353 }
354 instances, err := s.m.FindAllInstances(a.Name)
355 if err != nil {
356 return err
357 }
358 err = appTmpl.Execute(c.Response(), appContext[installer.StoreApp]{
359 App: a,
360 Instance: &instance,
361 Instances: instances,
362 AvailableNetworks: installer.CreateNetworks(global),
363 })
364 fmt.Println(err)
365 return err
366}
367
368func newTemplate() *template.Template {
369 return template.New("base").Funcs(template.FuncMap(sprig.FuncMap()))
370}