github.com/wtsi-ssg/wrstat@v1.1.4-0.20221008232152-3030622a8cf8/server/tree.go (about) 1 /******************************************************************************* 2 * Copyright (c) 2022 Genome Research Ltd. 3 * 4 * Author: Sendu Bala <sb10@sanger.ac.uk> 5 * 6 * Permission is hereby granted, free of charge, to any person obtaining 7 * a copy of this software and associated documentation files (the 8 * "Software"), to deal in the Software without restriction, including 9 * without limitation the rights to use, copy, modify, merge, publish, 10 * distribute, sublicense, and/or sell copies of the Software, and to 11 * permit persons to whom the Software is furnished to do so, subject to 12 * the following conditions: 13 * 14 * The above copyright notice and this permission notice shall be included 15 * in all copies or substantial portions of the Software. 16 * 17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 21 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 22 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 23 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 ******************************************************************************/ 25 26 package server 27 28 import ( 29 "io/fs" 30 "net/http" 31 "os" 32 "path/filepath" 33 "time" 34 35 "github.com/gin-gonic/gin" 36 "github.com/wtsi-ssg/wrstat/dgut" 37 ) 38 39 // javascriptToJSONFormat is the date format emitted by javascript's Date's 40 // toJSON method. It conforms to ISO 8601 and is like RFC3339 and in UTC. 41 const javascriptToJSONFormat = "2006-01-02T15:04:05.999Z" 42 43 // AddTreePage adds the /tree static web page to the server, along with the 44 // /rest/v1/auth/tree endpoint. It only works if EnableAuth() has been called 45 // first. 46 func (s *Server) AddTreePage() error { 47 if s.authGroup == nil { 48 return ErrNeedsAuth 49 } 50 51 fsys := getStaticFS() 52 53 s.router.StaticFS(TreePath, http.FS(fsys)) 54 55 s.router.NoRoute(func(c *gin.Context) { 56 c.Redirect(http.StatusMovedPermanently, "/tree/tree.html") 57 }) 58 59 s.authGroup.GET(TreePath, s.getTree) 60 61 return nil 62 } 63 64 // getStaticFS returns an FS for the static files needed for the tree webpage. 65 // Returns embedded files by default, or a live view of the git repo files if 66 // env var WRSTAT_SERVER_DEV is set to 1. 67 func getStaticFS() fs.FS { 68 var fsys fs.FS 69 70 treeDir := "static/tree" 71 72 if os.Getenv(devEnvKey) == devEnvVal { 73 fsys = os.DirFS(treeDir) 74 } else { 75 fsys, _ = fs.Sub(staticFS, treeDir) //nolint:errcheck 76 } 77 78 return fsys 79 } 80 81 // AddGroupAreas takes a map of area keys and group slice values. Clients will 82 // then receive this map on TreeElements in the "areas" field. 83 // 84 // If EnableAuth() has been called, also creates the /auth/group-areas endpoint 85 // that returns the given value. 86 func (s *Server) AddGroupAreas(areas map[string][]string) { 87 s.areas = areas 88 89 if s.authGroup != nil { 90 s.authGroup.GET(groupAreasPaths, s.getGroupAreas) 91 } 92 } 93 94 // getGroupAreas serves up our areas hash as JSON. 95 func (s *Server) getGroupAreas(c *gin.Context) { 96 c.IndentedJSON(http.StatusOK, s.areas) 97 } 98 99 // TreeElement holds tree.DirInfo type information in a form suited to passing 100 // to the treemap web interface. It also includes the server's dataTimeStamp so 101 // interfaces can report on how long ago the data forming the tree was 102 // captured. 103 type TreeElement struct { 104 Name string `json:"name"` 105 Path string `json:"path"` 106 Count uint64 `json:"count"` 107 Size uint64 `json:"size"` 108 Atime string `json:"atime"` 109 Users []string `json:"users"` 110 Groups []string `json:"groups"` 111 FileTypes []string `json:"filetypes"` 112 HasChildren bool `json:"has_children"` 113 Children []*TreeElement `json:"children,omitempty"` 114 TimeStamp string `json:"timestamp"` 115 Areas map[string][]string `json:"areas"` 116 } 117 118 // getTree responds with the data needed by the tree web interface. LoadDGUTDB() 119 // must already have been called. This is called when there is a GET on 120 // /rest/v1/auth/tree. 121 func (s *Server) getTree(c *gin.Context) { 122 path := c.DefaultQuery("path", "/") 123 124 filter, err := s.getFilter(c) 125 if err != nil { 126 c.AbortWithError(http.StatusBadRequest, err) //nolint:errcheck 127 128 return 129 } 130 131 s.treeMutex.RLock() 132 defer s.treeMutex.RUnlock() 133 134 di, err := s.tree.DirInfo(path, filter) 135 if err != nil { 136 c.AbortWithError(http.StatusBadRequest, err) //nolint:errcheck 137 138 return 139 } 140 141 c.JSON(http.StatusOK, s.diToTreeElement(di, filter)) 142 } 143 144 // diToTreeElement converts the given dgut.DirInfo to our own TreeElement. It 145 // has to do additional database queries to find out if di's children have 146 // children. 147 func (s *Server) diToTreeElement(di *dgut.DirInfo, filter *dgut.Filter) *TreeElement { 148 te := s.ddsToTreeElement(di.Current) 149 te.HasChildren = len(di.Children) > 0 150 childElements := make([]*TreeElement, len(di.Children)) 151 152 for i, dds := range di.Children { 153 childTE := s.ddsToTreeElement(dds) 154 childTE.HasChildren = s.tree.DirHasChildren(dds.Dir, filter) 155 childElements[i] = childTE 156 } 157 158 te.Children = childElements 159 te.Areas = s.areas 160 161 return te 162 } 163 164 // ddsToTreeElement converts a dgut.DirSummary to a TreeElement, but with no 165 // child info. 166 func (s *Server) ddsToTreeElement(dds *dgut.DirSummary) *TreeElement { 167 return &TreeElement{ 168 Name: filepath.Base(dds.Dir), 169 Path: dds.Dir, 170 Count: dds.Count, 171 Size: dds.Size, 172 Atime: timeToJavascriptDate(dds.Atime), 173 Users: s.uidsToUsernames(dds.UIDs), 174 Groups: s.gidsToNames(dds.GIDs), 175 FileTypes: s.ftsToNames(dds.FTs), 176 TimeStamp: timeToJavascriptDate(s.dataTimeStamp), 177 } 178 } 179 180 // timeToJavascriptDate returns the given time in javascript Date's toJSON 181 // format. 182 func timeToJavascriptDate(t time.Time) string { 183 return t.UTC().Format(javascriptToJSONFormat) 184 }