PLT-12 adding log viewer
Этот коммит содержится в:
51
api/admin.go
Обычный файл
51
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)))
|
||||||
|
}
|
||||||
35
api/admin_test.go
Обычный файл
35
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -41,6 +41,7 @@ func InitApi() {
|
|||||||
InitFile(r)
|
InitFile(r)
|
||||||
InitCommand(r)
|
InitCommand(r)
|
||||||
InitConfig(r)
|
InitConfig(r)
|
||||||
|
InitAdmin(r)
|
||||||
|
|
||||||
templatesDir := utils.FindDir("api/templates")
|
templatesDir := utils.FindDir("api/templates")
|
||||||
l4g.Debug("Parsing server templates at %v", templatesDir)
|
l4g.Debug("Parsing server templates at %v", templatesDir)
|
||||||
|
|||||||
@@ -292,6 +292,16 @@ func (c *Context) IsSystemAdmin() bool {
|
|||||||
return false
|
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 {
|
func (c *Context) IsTeamAdmin(userId string) bool {
|
||||||
if uresult := <-Srv.Store.User().Get(userId); uresult.Err != nil {
|
if uresult := <-Srv.Store.User().Get(userId); uresult.Err != nil {
|
||||||
c.Err = uresult.Err
|
c.Err = uresult.Err
|
||||||
|
|||||||
@@ -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) {
|
func (c *Client) CreateChannel(channel *Channel) (*Result, *AppError) {
|
||||||
if r, err := c.DoPost("/channels/create", channel.ToJson()); err != nil {
|
if r, err := c.DoPost("/channels/create", channel.ToJson()); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -11,9 +11,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
MODE_DEV = "dev"
|
MODE_DEV = "dev"
|
||||||
MODE_BETA = "beta"
|
MODE_BETA = "beta"
|
||||||
MODE_PROD = "prod"
|
MODE_PROD = "prod"
|
||||||
|
LOG_ROTATE_SIZE = 10000
|
||||||
)
|
)
|
||||||
|
|
||||||
type ServiceSettings struct {
|
type ServiceSettings struct {
|
||||||
@@ -180,10 +181,10 @@ func ConfigureCmdLineLog() {
|
|||||||
ls.ConsoleEnable = true
|
ls.ConsoleEnable = true
|
||||||
ls.ConsoleLevel = "ERROR"
|
ls.ConsoleLevel = "ERROR"
|
||||||
ls.FileEnable = false
|
ls.FileEnable = false
|
||||||
configureLog(ls)
|
configureLog(&ls)
|
||||||
}
|
}
|
||||||
|
|
||||||
func configureLog(s LogSettings) {
|
func configureLog(s *LogSettings) {
|
||||||
|
|
||||||
l4g.Close()
|
l4g.Close()
|
||||||
|
|
||||||
@@ -217,7 +218,7 @@ func configureLog(s LogSettings) {
|
|||||||
flw := l4g.NewFileLogWriter(s.FileLocation, false)
|
flw := l4g.NewFileLogWriter(s.FileLocation, false)
|
||||||
flw.SetFormat(s.FileFormat)
|
flw.SetFormat(s.FileFormat)
|
||||||
flw.SetRotate(true)
|
flw.SetRotate(true)
|
||||||
flw.SetRotateLines(100000)
|
flw.SetRotateLines(LOG_ROTATE_SIZE)
|
||||||
l4g.AddFilter("file", level, flw)
|
l4g.AddFilter("file", level, flw)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -241,7 +242,7 @@ func LoadConfig(fileName string) {
|
|||||||
panic("Error decoding config file=" + fileName + ", err=" + err.Error())
|
panic("Error decoding config file=" + fileName + ", err=" + err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
configureLog(config.LogSettings)
|
configureLog(&config.LogSettings)
|
||||||
|
|
||||||
Cfg = &config
|
Cfg = &config
|
||||||
SanitizeOptions = getSanitizeOptions()
|
SanitizeOptions = getSanitizeOptions()
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
var AdminSidebar = require('./admin_sidebar.jsx');
|
var AdminSidebar = require('./admin_sidebar.jsx');
|
||||||
var EmailTab = require('./email_settings.jsx');
|
var EmailTab = require('./email_settings.jsx');
|
||||||
var JobsTab = require('./jobs_settings.jsx');
|
var JobsTab = require('./jobs_settings.jsx');
|
||||||
|
var LogsTab = require('./logs.jsx');
|
||||||
var Navbar = require('../../components/navbar.jsx');
|
var Navbar = require('../../components/navbar.jsx');
|
||||||
|
|
||||||
export default class AdminController extends React.Component {
|
export default class AdminController extends React.Component {
|
||||||
@@ -28,6 +29,8 @@ export default class AdminController extends React.Component {
|
|||||||
tab = <EmailTab />;
|
tab = <EmailTab />;
|
||||||
} else if (this.state.selected === 'job_settings') {
|
} else if (this.state.selected === 'job_settings') {
|
||||||
tab = <JobsTab />;
|
tab = <JobsTab />;
|
||||||
|
} else if (this.state.selected === 'logs') {
|
||||||
|
tab = <LogsTab />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -83,7 +83,15 @@ export default class AdminSidebar extends React.Component {
|
|||||||
{'Email Settings'}
|
{'Email Settings'}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</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>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
|
|||||||
98
web/react/components/admin_console/logs.jsx
Обычный файл
98
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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
67
web/react/stores/admin_store.jsx
Обычный файл
67
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;
|
||||||
@@ -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) {
|
export function findTeams(email) {
|
||||||
if (isCallInProgress('findTeams_' + email)) {
|
if (isCallInProgress('findTeams_' + email)) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -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) {
|
export function getMeSynchronous(success, error) {
|
||||||
var currentUser = null;
|
var currentUser = null;
|
||||||
$.ajax({
|
$.ajax({
|
||||||
|
|||||||
@@ -34,7 +34,9 @@ module.exports = {
|
|||||||
CLICK_TEAM: null,
|
CLICK_TEAM: null,
|
||||||
RECIEVED_TEAM: null,
|
RECIEVED_TEAM: null,
|
||||||
|
|
||||||
RECIEVED_CONFIG: null
|
RECIEVED_CONFIG: null,
|
||||||
|
|
||||||
|
RECIEVED_LOGS: null
|
||||||
}),
|
}),
|
||||||
|
|
||||||
PayloadSources: keyMirror({
|
PayloadSources: keyMirror({
|
||||||
|
|||||||
10
web/web.go
10
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) {
|
func adminConsole(c *api.Context, w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
if !c.IsSystemAdmin() {
|
if !c.HasSystemAdminPermissions("adminConsole") {
|
||||||
c.Err = model.NewAppError("adminConsole", "You do not have permission to access the admin console.", "")
|
|
||||||
c.Err.StatusCode = http.StatusForbidden
|
|
||||||
return
|
return
|
||||||
} else {
|
|
||||||
page := NewHtmlTemplatePage("admin_console", "Admin Console")
|
|
||||||
page.Render(c, w)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
page := NewHtmlTemplatePage("admin_console", "Admin Console")
|
||||||
|
page.Render(c, w)
|
||||||
}
|
}
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user