blob: 242faed0efbdcc1ff1742ba9a01e93c104911890 [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 {
31 port int
32 webAppAddr string
33 m *installer.AppManager
34 r installer.AppRepository[installer.StoreApp]
35}
36
37func NewAppManagerServer(
38 port int,
39 webAppAddr string,
40 m *installer.AppManager,
41 r installer.AppRepository[installer.StoreApp],
42) *AppManagerServer {
43 return &AppManagerServer{
44 port,
45 webAppAddr,
46 m,
47 r,
48 }
49}
50
51func (s *AppManagerServer) Start() {
52 e := echo.New()
53 e.StaticFS("/static", echo.MustSubFS(staticAssets, "static"))
54 e.GET("/api/app-repo", s.handleAppRepo)
55 e.POST("/api/app/:slug/render", s.handleAppRender)
56 e.POST("/api/app/:slug/install", s.handleAppInstall)
57 e.GET("/api/app/:slug", s.handleApp)
58 e.GET("/api/instance/:slug", s.handleInstance)
59 e.POST("/api/instance/:slug/update", s.handleAppUpdate)
60 e.POST("/api/instance/:slug/remove", s.handleAppRemove)
61 e.GET("/", s.handleIndex)
62 e.GET("/app/:slug", s.handleAppUI)
63 e.GET("/instance/:slug", s.handleInstanceUI)
64 fmt.Printf("Starting HTTP server on port: %d\n", s.port)
65 log.Fatal(e.Start(fmt.Sprintf(":%d", s.port)))
66}
67
68type app struct {
69 Name string `json:"name"`
70 Icon template.HTML `json:"icon"`
71 ShortDescription string `json:"shortDescription"`
72 Slug string `json:"slug"`
73 Schema string `json:"schema"`
74 Instances []installer.AppConfig `json:"instances,omitempty"`
75}
76
77func (s *AppManagerServer) handleAppRepo(c echo.Context) error {
78 all, err := s.r.GetAll()
79 if err != nil {
80 return err
81 }
82 resp := make([]app, len(all))
83 for i, a := range all {
84 resp[i] = app{a.Name, a.Icon, a.ShortDescription, a.Name, a.Schema, nil}
85 }
86 return c.JSON(http.StatusOK, resp)
87}
88
89func (s *AppManagerServer) handleApp(c echo.Context) error {
90 slug := c.Param("slug")
91 a, err := s.r.Find(slug)
92 if err != nil {
93 return err
94 }
95 instances, err := s.m.FindAllInstances(slug)
96 if err != nil {
97 return err
98 }
99 for _, instance := range instances {
100 values, ok := instance.Config["Values"].(map[string]any)
101 if !ok {
102 return fmt.Errorf("Expected map")
103 }
104 for k, v := range values {
105 if k == "Network" {
106 n, ok := v.(map[string]any)
107 if !ok {
108 return fmt.Errorf("Expected map")
109 }
110 values["Network"], ok = n["Name"]
111 if !ok {
112 return fmt.Errorf("Missing Name")
113 }
114 break
115 }
116 }
117 }
118 return c.JSON(http.StatusOK, app{a.Name, a.Icon, a.ShortDescription, a.Name, a.Schema, instances})
119}
120
121func (s *AppManagerServer) handleInstance(c echo.Context) error {
122 slug := c.Param("slug")
123 instance, err := s.m.FindInstance(slug)
124 if err != nil {
125 return err
126 }
127 values, ok := instance.Config["Values"].(map[string]any)
128 if !ok {
129 return fmt.Errorf("Expected map")
130 }
131 for k, v := range values {
132 if k == "Network" {
133 n, ok := v.(map[string]any)
134 if !ok {
135 return fmt.Errorf("Expected map")
136 }
137 values["Network"], ok = n["Name"]
138 if !ok {
139 return fmt.Errorf("Missing Name")
140 }
141 break
142 }
143 }
144 a, err := s.r.Find(instance.AppId)
145 if err != nil {
146 return err
147 }
148 return c.JSON(http.StatusOK, app{a.Name, a.Icon, a.ShortDescription, a.Name, a.Schema, []installer.AppConfig{instance}})
149}
150
151type file struct {
152 Name string `json:"name"`
153 Contents string `json:"contents"`
154}
155
156type rendered struct {
157 Readme string `json:"readme"`
158 Files []file `json:"files"`
159}
160
161func (s *AppManagerServer) handleAppRender(c echo.Context) error {
162 slug := c.Param("slug")
163 contents, err := ioutil.ReadAll(c.Request().Body)
164 if err != nil {
165 return err
166 }
167 global, err := s.m.Config()
168 if err != nil {
169 return err
170 }
171 var values map[string]any
172 if err := json.Unmarshal(contents, &values); err != nil {
173 return err
174 }
175 if network, ok := values["Network"]; ok {
176 for _, n := range installer.CreateNetworks(global) {
177 if n.Name == network { // TODO(giolekva): handle not found
178 values["Network"] = n
179 }
180 }
181 }
182 all := map[string]any{
183 "Global": global.Values,
184 "Values": values,
185 }
186 a, err := s.r.Find(slug)
187 if err != nil {
188 return err
189 }
190 var readme bytes.Buffer
191 if err := a.Readme.Execute(&readme, all); err != nil {
192 return err
193 }
194 var resp rendered
195 resp.Readme = readme.String()
196 for _, tmpl := range a.Templates { // TODO(giolekva): deduplicate with Install
197 var f bytes.Buffer
198 if err := tmpl.Execute(&f, all); err != nil {
199 fmt.Printf("%+v\n", all)
200 fmt.Println(err.Error())
201 return err
202 } else {
203 resp.Files = append(resp.Files, file{tmpl.Name(), f.String()})
204 }
205 }
206 out, err := json.Marshal(resp)
207 if err != nil {
208 return err
209 }
210 if _, err := c.Response().Writer.Write(out); err != nil {
211 return err
212 }
213 return nil
214}
215
216func (s *AppManagerServer) handleAppInstall(c echo.Context) error {
217 slug := c.Param("slug")
218 contents, err := ioutil.ReadAll(c.Request().Body)
219 if err != nil {
220 return err
221 }
222 var values map[string]any
223 if err := json.Unmarshal(contents, &values); err != nil {
224 return err
225 }
226 fmt.Println(values)
227 a, err := s.r.Find(slug)
228 if err != nil {
229 return err
230 }
231 config, err := s.m.Config()
232 if err != nil {
233 return err
234 }
235 nsGen := installer.NewPrefixGenerator(config.Values.NamespacePrefix)
236 suffixGen := installer.NewFixedLengthRandomSuffixGenerator(3)
237 if err := s.m.Install(a.App, nsGen, suffixGen, values); err != nil {
238 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}