github.com/vnforks/kid/v5@v5.22.1-0.20200408055009-b89d99c65676/app/admin.go (about) 1 // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. 2 // See LICENSE.txt for license information. 3 4 package app 5 6 import ( 7 "fmt" 8 "io" 9 "os" 10 "time" 11 12 "runtime/debug" 13 14 "net/http" 15 16 "github.com/vnforks/kid/v5/mlog" 17 "github.com/vnforks/kid/v5/model" 18 "github.com/vnforks/kid/v5/services/mailservice" 19 "github.com/vnforks/kid/v5/utils" 20 ) 21 22 func (a *App) GetLogs(page, perPage int) ([]string, *model.AppError) { 23 var lines []string 24 if a.Cluster() != nil && *a.Config().ClusterSettings.Enable { 25 lines = append(lines, "-----------------------------------------------------------------------------------------------------------") 26 lines = append(lines, "-----------------------------------------------------------------------------------------------------------") 27 lines = append(lines, a.Cluster().GetMyClusterInfo().Hostname) 28 lines = append(lines, "-----------------------------------------------------------------------------------------------------------") 29 lines = append(lines, "-----------------------------------------------------------------------------------------------------------") 30 } 31 32 melines, err := a.GetLogsSkipSend(page, perPage) 33 if err != nil { 34 return nil, err 35 } 36 37 lines = append(lines, melines...) 38 39 if a.Cluster() != nil && *a.Config().ClusterSettings.Enable { 40 clines, err := a.Cluster().GetLogs(page, perPage) 41 if err != nil { 42 return nil, err 43 } 44 45 lines = append(lines, clines...) 46 } 47 48 return lines, nil 49 } 50 51 func (a *App) GetLogsSkipSend(page, perPage int) ([]string, *model.AppError) { 52 var lines []string 53 54 if *a.Config().LogSettings.EnableFile { 55 logFile := utils.GetLogFileLocation(*a.Config().LogSettings.FileLocation) 56 file, err := os.Open(logFile) 57 if err != nil { 58 return nil, model.NewAppError("getLogs", "api.admin.file_read_error", nil, err.Error(), http.StatusInternalServerError) 59 } 60 61 defer file.Close() 62 63 var newLine = []byte{'\n'} 64 var lineCount int 65 const searchPos = -1 66 b := make([]byte, 1) 67 var endOffset int64 = 0 68 69 // if the file exists and it's last byte is '\n' - skip it 70 var stat os.FileInfo 71 if stat, err = os.Stat(logFile); err == nil { 72 if _, err = file.ReadAt(b, stat.Size()-1); err == nil && b[0] == newLine[0] { 73 endOffset = -1 74 } 75 } 76 lineEndPos, err := file.Seek(endOffset, io.SeekEnd) 77 if err != nil { 78 return nil, model.NewAppError("getLogs", "api.admin.file_read_error", nil, err.Error(), http.StatusInternalServerError) 79 } 80 for { 81 pos, err := file.Seek(searchPos, io.SeekCurrent) 82 if err != nil { 83 return nil, model.NewAppError("getLogs", "api.admin.file_read_error", nil, err.Error(), http.StatusInternalServerError) 84 } 85 86 _, err = file.ReadAt(b, pos) 87 if err != nil { 88 return nil, model.NewAppError("getLogs", "api.admin.file_read_error", nil, err.Error(), http.StatusInternalServerError) 89 } 90 91 if b[0] == newLine[0] || pos == 0 { 92 lineCount++ 93 if lineCount > page*perPage { 94 line := make([]byte, lineEndPos-pos) 95 _, err := file.ReadAt(line, pos) 96 if err != nil { 97 return nil, model.NewAppError("getLogs", "api.admin.file_read_error", nil, err.Error(), http.StatusInternalServerError) 98 } 99 lines = append(lines, string(line)) 100 } 101 if pos == 0 { 102 break 103 } 104 lineEndPos = pos 105 } 106 107 if len(lines) == perPage { 108 break 109 } 110 } 111 112 for i, j := 0, len(lines)-1; i < j; i, j = i+1, j-1 { 113 lines[i], lines[j] = lines[j], lines[i] 114 } 115 } else { 116 lines = append(lines, "") 117 } 118 119 return lines, nil 120 } 121 122 func (a *App) GetClusterStatus() []*model.ClusterInfo { 123 infos := make([]*model.ClusterInfo, 0) 124 125 if a.Cluster() != nil { 126 infos = a.Cluster().GetClusterInfos() 127 } 128 129 return infos 130 } 131 132 func (a *App) InvalidateAllCaches() *model.AppError { 133 debug.FreeOSMemory() 134 a.InvalidateAllCachesSkipSend() 135 136 if a.Cluster() != nil { 137 138 msg := &model.ClusterMessage{ 139 Event: model.CLUSTER_EVENT_INVALIDATE_ALL_CACHES, 140 SendType: model.CLUSTER_SEND_RELIABLE, 141 WaitForAllToSend: true, 142 } 143 144 a.Cluster().SendClusterMessage(msg) 145 } 146 147 return nil 148 } 149 150 func (a *App) InvalidateAllCachesSkipSend() { 151 mlog.Info("Purging all caches") 152 a.Srv().sessionCache.Purge() 153 a.Srv().statusCache.Purge() 154 a.Srv().Store.Branch().ClearCaches() 155 a.Srv().Store.Class().ClearCaches() 156 a.Srv().Store.User().ClearCaches() 157 a.Srv().Store.FileInfo().ClearCaches() 158 a.Srv().Store.Webhook().ClearCaches() 159 a.LoadLicense() 160 } 161 162 func (a *App) RecycleDatabaseConnection() { 163 oldStore := a.Srv().Store 164 165 mlog.Warn("Attempting to recycle the database connection.") 166 a.Srv().Store = a.Srv().newStore() 167 a.Srv().Jobs.Store = a.Srv().Store 168 169 if a.Srv().Store != oldStore { 170 time.Sleep(20 * time.Second) 171 oldStore.Close() 172 } 173 174 mlog.Warn("Finished recycling the database connection.") 175 } 176 177 func (a *App) TestSiteURL(siteURL string) *model.AppError { 178 url := fmt.Sprintf("%s/api/v4/system/ping", siteURL) 179 res, err := http.Get(url) 180 if err != nil || res.StatusCode != 200 { 181 return model.NewAppError("testSiteURL", "app.admin.test_site_url.failure", nil, "", http.StatusBadRequest) 182 } 183 184 return nil 185 } 186 187 func (a *App) TestEmail(userId string, cfg *model.Config) *model.AppError { 188 if len(*cfg.EmailSettings.SMTPServer) == 0 { 189 return model.NewAppError("testEmail", "api.admin.test_email.missing_server", nil, utils.T("api.context.invalid_param.app_error", map[string]interface{}{"Name": "SMTPServer"}), http.StatusBadRequest) 190 } 191 192 if !*cfg.EmailSettings.SendEmailNotifications { 193 return nil 194 } 195 196 // if the user hasn't changed their email settings, fill in the actual SMTP password so that 197 // the user can verify an existing SMTP connection 198 if *cfg.EmailSettings.SMTPPassword == model.FAKE_SETTING { 199 if *cfg.EmailSettings.SMTPServer == *a.Config().EmailSettings.SMTPServer && 200 *cfg.EmailSettings.SMTPPort == *a.Config().EmailSettings.SMTPPort && 201 *cfg.EmailSettings.SMTPUsername == *a.Config().EmailSettings.SMTPUsername { 202 *cfg.EmailSettings.SMTPPassword = *a.Config().EmailSettings.SMTPPassword 203 } else { 204 return model.NewAppError("testEmail", "api.admin.test_email.reenter_password", nil, "", http.StatusBadRequest) 205 } 206 } 207 user, err := a.GetUser(userId) 208 if err != nil { 209 return err 210 } 211 212 T := utils.GetUserTranslations(user.Locale) 213 license := a.License() 214 if err := mailservice.SendMailUsingConfig(user.Email, T("api.admin.test_email.subject"), T("api.admin.test_email.body"), cfg, license != nil && *license.Features.Compliance); err != nil { 215 return model.NewAppError("testEmail", "app.admin.test_email.failure", map[string]interface{}{"Error": err.Error()}, "", http.StatusInternalServerError) 216 } 217 218 return nil 219 } 220 221 // ServerBusyStateChanged is called when a CLUSTER_EVENT_BUSY_STATE_CHANGED is received. 222 func (a *App) ServerBusyStateChanged(sbs *model.ServerBusyState) { 223 a.Srv().Busy.ClusterEventChanged(sbs) 224 if sbs.Busy { 225 mlog.Warn("server busy state activitated via cluster event - non-critical services disabled", mlog.Int64("expires_sec", sbs.Expires)) 226 } else { 227 mlog.Info("server busy state cleared via cluster event - non-critical services enabled") 228 } 229 }