summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
author=Corey Hulen <corey@hulen.com>2015-09-10 18:32:22 -0700
committer=Corey Hulen <corey@hulen.com>2015-09-10 18:32:22 -0700
commite06e292be71ca699d90bafbd635118aa47c2d7a5 (patch)
tree3f8f7ce86a1618f625e71084041746745139ba0c
parent41439eb801c6c8c0a55bcada3eeba3b4a561c663 (diff)
downloadchat-e06e292be71ca699d90bafbd635118aa47c2d7a5.tar.gz
chat-e06e292be71ca699d90bafbd635118aa47c2d7a5.tar.bz2
chat-e06e292be71ca699d90bafbd635118aa47c2d7a5.zip
PLT-12 adding log viewer
-rw-r--r--api/admin.go51
-rw-r--r--api/admin_test.go35
-rw-r--r--api/api.go1
-rw-r--r--api/context.go10
-rw-r--r--model/client.go9
-rw-r--r--utils/config.go15
-rw-r--r--web/react/components/admin_console/admin_controller.jsx3
-rw-r--r--web/react/components/admin_console/admin_sidebar.jsx10
-rw-r--r--web/react/components/admin_console/logs.jsx98
-rw-r--r--web/react/stores/admin_store.jsx67
-rw-r--r--web/react/utils/async_client.jsx26
-rw-r--r--web/react/utils/client.jsx14
-rw-r--r--web/react/utils/constants.jsx4
-rw-r--r--web/web.go10
14 files changed, 338 insertions, 15 deletions
diff --git a/api/admin.go b/api/admin.go
new file mode 100644
index 000000000..d4af1d247
--- /dev/null
+++ b/api/admin.go
@@ -0,0 +1,51 @@
+// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package api
+
+import (
+ "bufio"
+ "net/http"
+ "os"
+
+ l4g "code.google.com/p/log4go"
+ "github.com/gorilla/mux"
+
+ "github.com/mattermost/platform/model"
+ "github.com/mattermost/platform/utils"
+)
+
+func InitAdmin(r *mux.Router) {
+ l4g.Debug("Initializing admin api routes")
+
+ sr := r.PathPrefix("/admin").Subrouter()
+ sr.Handle("/logs", ApiUserRequired(getLogs)).Methods("GET")
+}
+
+func getLogs(c *Context, w http.ResponseWriter, r *http.Request) {
+
+ if !c.HasSystemAdminPermissions("getLogs") {
+ return
+ }
+
+ var lines []string
+
+ if utils.Cfg.LogSettings.FileEnable {
+
+ file, err := os.Open(utils.Cfg.LogSettings.FileLocation)
+ if err != nil {
+ c.Err = model.NewAppError("getLogs", "Error reading log file", err.Error())
+ }
+
+ defer file.Close()
+
+ scanner := bufio.NewScanner(file)
+ for scanner.Scan() {
+ lines = append(lines, scanner.Text())
+ }
+ } else {
+ lines = append(lines, "")
+ }
+
+ w.Write([]byte(model.ArrayToJson(lines)))
+}
diff --git a/api/admin_test.go b/api/admin_test.go
new file mode 100644
index 000000000..460ac1208
--- /dev/null
+++ b/api/admin_test.go
@@ -0,0 +1,35 @@
+// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package api
+
+import (
+ "testing"
+
+ "github.com/mattermost/platform/model"
+ "github.com/mattermost/platform/store"
+)
+
+func TestGetLogs(t *testing.T) {
+ Setup()
+
+ team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
+ team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
+
+ user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
+ store.Must(Srv.Store.User().VerifyEmail(user.Id))
+
+ c := &Context{}
+ c.RequestId = model.NewId()
+ c.IpAddress = "cmd_line"
+ UpdateRoles(c, user, model.ROLE_SYSTEM_ADMIN)
+
+ Client.LoginByEmail(team.Name, user.Email, "pwd")
+
+ if logs, err := Client.GetLogs(); err != nil {
+ t.Fatal(err)
+ } else if len(logs.Data.([]string)) <= 0 {
+ t.Fatal()
+ }
+}
diff --git a/api/api.go b/api/api.go
index 9770930f7..35ac0bdc0 100644
--- a/api/api.go
+++ b/api/api.go
@@ -41,6 +41,7 @@ func InitApi() {
InitFile(r)
InitCommand(r)
InitConfig(r)
+ InitAdmin(r)
templatesDir := utils.FindDir("api/templates")
l4g.Debug("Parsing server templates at %v", templatesDir)
diff --git a/api/context.go b/api/context.go
index 1852ed4d6..ea5677f95 100644
--- a/api/context.go
+++ b/api/context.go
@@ -292,6 +292,16 @@ func (c *Context) IsSystemAdmin() bool {
return false
}
+func (c *Context) HasSystemAdminPermissions(where string) bool {
+ if c.IsSystemAdmin() {
+ return true
+ }
+
+ c.Err = model.NewAppError(where, "You do not have the appropriate permissions", "userId="+c.Session.UserId)
+ c.Err.StatusCode = http.StatusForbidden
+ return false
+}
+
func (c *Context) IsTeamAdmin(userId string) bool {
if uresult := <-Srv.Store.User().Get(userId); uresult.Err != nil {
c.Err = uresult.Err
diff --git a/model/client.go b/model/client.go
index c355b90f5..5aac09289 100644
--- a/model/client.go
+++ b/model/client.go
@@ -338,6 +338,15 @@ func (c *Client) GetAudits(id string, etag string) (*Result, *AppError) {
}
}
+func (c *Client) GetLogs() (*Result, *AppError) {
+ if r, err := c.DoGet("/admin/logs", "", ""); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), ArrayFromJson(r.Body)}, nil
+ }
+}
+
func (c *Client) CreateChannel(channel *Channel) (*Result, *AppError) {
if r, err := c.DoPost("/channels/create", channel.ToJson()); err != nil {
return nil, err
diff --git a/utils/config.go b/utils/config.go
index c67e17e79..a1d282c29 100644
--- a/utils/config.go
+++ b/utils/config.go
@@ -11,9 +11,10 @@ import (
)
const (
- MODE_DEV = "dev"
- MODE_BETA = "beta"
- MODE_PROD = "prod"
+ MODE_DEV = "dev"
+ MODE_BETA = "beta"
+ MODE_PROD = "prod"
+ LOG_ROTATE_SIZE = 10000
)
type ServiceSettings struct {
@@ -180,10 +181,10 @@ func ConfigureCmdLineLog() {
ls.ConsoleEnable = true
ls.ConsoleLevel = "ERROR"
ls.FileEnable = false
- configureLog(ls)
+ configureLog(&ls)
}
-func configureLog(s LogSettings) {
+func configureLog(s *LogSettings) {
l4g.Close()
@@ -217,7 +218,7 @@ func configureLog(s LogSettings) {
flw := l4g.NewFileLogWriter(s.FileLocation, false)
flw.SetFormat(s.FileFormat)
flw.SetRotate(true)
- flw.SetRotateLines(100000)
+ flw.SetRotateLines(LOG_ROTATE_SIZE)
l4g.AddFilter("file", level, flw)
}
}
@@ -241,7 +242,7 @@ func LoadConfig(fileName string) {
panic("Error decoding config file=" + fileName + ", err=" + err.Error())
}
- configureLog(config.LogSettings)
+ configureLog(&config.LogSettings)
Cfg = &config
SanitizeOptions = getSanitizeOptions()
diff --git a/web/react/components/admin_console/admin_controller.jsx b/web/react/components/admin_console/admin_controller.jsx
index bb43af802..68984c9e0 100644
--- a/web/react/components/admin_console/admin_controller.jsx
+++ b/web/react/components/admin_console/admin_controller.jsx
@@ -4,6 +4,7 @@
var AdminSidebar = require('./admin_sidebar.jsx');
var EmailTab = require('./email_settings.jsx');
var JobsTab = require('./jobs_settings.jsx');
+var LogsTab = require('./logs.jsx');
var Navbar = require('../../components/navbar.jsx');
export default class AdminController extends React.Component {
@@ -28,6 +29,8 @@ export default class AdminController extends React.Component {
tab = <EmailTab />;
} else if (this.state.selected === 'job_settings') {
tab = <JobsTab />;
+ } else if (this.state.selected === 'logs') {
+ tab = <LogsTab />;
}
return (
diff --git a/web/react/components/admin_console/admin_sidebar.jsx b/web/react/components/admin_console/admin_sidebar.jsx
index 6b3be89d0..a04bceef5 100644
--- a/web/react/components/admin_console/admin_sidebar.jsx
+++ b/web/react/components/admin_console/admin_sidebar.jsx
@@ -83,7 +83,15 @@ export default class AdminSidebar extends React.Component {
{'Email Settings'}
</a>
</li>
- <li><a href='#'>{'Other Settings'}</a></li>
+ <li>
+ <a
+ href='#'
+ className={this.isSelected('logs')}
+ onClick={this.handleClick.bind(null, 'logs')}
+ >
+ {'Logs'}
+ </a>
+ </li>
</ul>
</li>
<li>
diff --git a/web/react/components/admin_console/logs.jsx b/web/react/components/admin_console/logs.jsx
new file mode 100644
index 000000000..959d99a30
--- /dev/null
+++ b/web/react/components/admin_console/logs.jsx
@@ -0,0 +1,98 @@
+// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+var AdminStore = require('../../stores/admin_store.jsx');
+var LoadingScreen = require('../loading_screen.jsx');
+var AsyncClient = require('../../utils/async_client.jsx');
+
+export default class Logs extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.onLogListenerChange = this.onLogListenerChange.bind(this);
+ this.reload = this.reload.bind(this);
+
+ this.state = {
+ logs: AdminStore.getLogs()
+ };
+ }
+
+ componentDidMount() {
+ AdminStore.addLogChangeListener(this.onLogListenerChange);
+ AsyncClient.getLogs();
+ }
+ componentWillUnmount() {
+ AdminStore.removeLogChangeListener(this.onLogListenerChange);
+ }
+ onLogListenerChange() {
+ this.setState({
+ logs: AdminStore.getLogs()
+ });
+ }
+
+ reload() {
+ AdminStore.saveLogs(null);
+ this.setState({
+ logs: null
+ });
+
+ AsyncClient.getLogs();
+ }
+
+ render() {
+ var content = null;
+
+ if (this.state.logs === null) {
+ content = <LoadingScreen />;
+ } else {
+ content = [];
+
+ for (var i = 0; i < this.state.logs.length; i++) {
+ var style = {
+ whiteSpace: 'nowrap',
+ fontFamily: 'monospace'
+ };
+
+ if (this.state.logs[i].indexOf('[EROR]') > 0) {
+ style.color = 'red';
+ }
+
+ content.push(<br key={'br_' + i} />);
+ content.push(
+ <span
+ key={'log_' + i}
+ style={style}
+ >
+ {this.state.logs[i]}
+ </span>
+ );
+ }
+ }
+
+ var divStyle = {
+ overflow: 'scroll',
+ width: '100%',
+ height: '800px',
+ border: '1px solid #ddd',
+ marginTop: '10px',
+ padding: '5px',
+ backgroundColor: 'white'
+ };
+
+ return (
+ <div className='panel'>
+ <h3>{'Server Logs'}</h3>
+ <button
+ type='submit'
+ className='btn btn-primary'
+ onClick={this.reload}
+ >
+ {'Reload'}
+ </button>
+ <div style={divStyle}>
+ {content}
+ </div>
+ </div>
+ );
+ }
+} \ No newline at end of file
diff --git a/web/react/stores/admin_store.jsx b/web/react/stores/admin_store.jsx
new file mode 100644
index 000000000..ac474cd4b
--- /dev/null
+++ b/web/react/stores/admin_store.jsx
@@ -0,0 +1,67 @@
+// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
+var EventEmitter = require('events').EventEmitter;
+
+var BrowserStore = require('../stores/browser_store.jsx');
+
+var Constants = require('../utils/constants.jsx');
+var ActionTypes = Constants.ActionTypes;
+
+var LOG_CHANGE_EVENT = 'log_change';
+
+class AdminStoreClass extends EventEmitter {
+ constructor() {
+ super();
+
+ this.logs = null;
+
+ this.emitLogChange = this.emitLogChange.bind(this);
+ this.addLogChangeListener = this.addLogChangeListener.bind(this);
+ this.removeLogChangeListener = this.removeLogChangeListener.bind(this);
+ }
+
+ emitLogChange() {
+ this.emit(LOG_CHANGE_EVENT);
+ }
+
+ addLogChangeListener(callback) {
+ this.on(LOG_CHANGE_EVENT, callback);
+ }
+
+ removeLogChangeListener(callback) {
+ this.removeListener(LOG_CHANGE_EVENT, callback);
+ }
+
+ getLogs() {
+ //return BrowserStore.getItem('logs');
+ return this.logs;
+ }
+
+ saveLogs(logs) {
+ // if (logs === null) {
+ // BrowserStore.removeItem('logs');
+ // } else {
+ // BrowserStore.setItem('logs', logs);
+ // }
+
+ this.logs = logs;
+ }
+}
+
+var AdminStore = new AdminStoreClass();
+
+AdminStoreClass.dispatchToken = AppDispatcher.register((payload) => {
+ var action = payload.action;
+
+ switch (action.type) {
+ case ActionTypes.RECIEVED_LOGS:
+ AdminStore.saveLogs(action.logs);
+ AdminStore.emitLogChange();
+ break;
+ default:
+ }
+});
+
+export default AdminStore;
diff --git a/web/react/utils/async_client.jsx b/web/react/utils/async_client.jsx
index 6ccef0506..6b8e73c5a 100644
--- a/web/react/utils/async_client.jsx
+++ b/web/react/utils/async_client.jsx
@@ -319,6 +319,32 @@ export function getAudits() {
);
}
+export function getLogs() {
+ if (isCallInProgress('getLogs')) {
+ return;
+ }
+
+ callTracker.getLogs = utils.getTimestamp();
+ client.getLogs(
+ (data, textStatus, xhr) => {
+ callTracker.getLogs = 0;
+
+ if (xhr.status === 304 || !data) {
+ return;
+ }
+
+ AppDispatcher.handleServerAction({
+ type: ActionTypes.RECIEVED_LOGS,
+ logs: data
+ });
+ },
+ (err) => {
+ callTracker.getLogs = 0;
+ dispatchError(err, 'getLogs');
+ }
+ );
+}
+
export function findTeams(email) {
if (isCallInProgress('findTeams_' + email)) {
return;
diff --git a/web/react/utils/client.jsx b/web/react/utils/client.jsx
index 51fd16474..75ffdb274 100644
--- a/web/react/utils/client.jsx
+++ b/web/react/utils/client.jsx
@@ -294,6 +294,20 @@ export function getAudits(userId, success, error) {
});
}
+export function getLogs(success, error) {
+ $.ajax({
+ url: '/api/v1/admin/logs',
+ dataType: 'json',
+ contentType: 'application/json',
+ type: 'GET',
+ success: success,
+ error: function onError(xhr, status, err) {
+ var e = handleError('getLogs', xhr, status, err);
+ error(e);
+ }
+ });
+}
+
export function getMeSynchronous(success, error) {
var currentUser = null;
$.ajax({
diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx
index 7ead079d7..03e4635b5 100644
--- a/web/react/utils/constants.jsx
+++ b/web/react/utils/constants.jsx
@@ -34,7 +34,9 @@ module.exports = {
CLICK_TEAM: null,
RECIEVED_TEAM: null,
- RECIEVED_CONFIG: null
+ RECIEVED_CONFIG: null,
+
+ RECIEVED_LOGS: null
}),
PayloadSources: keyMirror({
diff --git a/web/web.go b/web/web.go
index 9cb81226b..1ed055a62 100644
--- a/web/web.go
+++ b/web/web.go
@@ -643,12 +643,10 @@ func loginCompleteOAuth(c *api.Context, w http.ResponseWriter, r *http.Request)
func adminConsole(c *api.Context, w http.ResponseWriter, r *http.Request) {
- if !c.IsSystemAdmin() {
- c.Err = model.NewAppError("adminConsole", "You do not have permission to access the admin console.", "")
- c.Err.StatusCode = http.StatusForbidden
+ if !c.HasSystemAdminPermissions("adminConsole") {
return
- } else {
- page := NewHtmlTemplatePage("admin_console", "Admin Console")
- page.Render(c, w)
}
+
+ page := NewHtmlTemplatePage("admin_console", "Admin Console")
+ page.Render(c, w)
}