diff options
-rw-r--r-- | js/App.js | 42 | ||||
-rw-r--r-- | js/Components/LogUploader.js | 40 | ||||
-rw-r--r-- | js/Components/LogUploader.less | 3 | ||||
-rw-r--r-- | js/Providers/LogFile.js | 16 | ||||
-rw-r--r-- | js/Providers/LogProvider.js | 7 | ||||
-rw-r--r-- | package.json | 1 |
6 files changed, 97 insertions, 12 deletions
@@ -4,9 +4,11 @@ import ReactScrolla from 'react-scrolla'; import {LogProvider} from './Providers/LogProvider.js'; import {LogTable} from './Components/LogTable.js'; import {ToggleEntry} from './Components/ToggleEntry.js'; -import {App as AppContainer, SideBar, Content} from 'oc-react-components'; +import {LogUploader} from './Components/LogUploader.js'; +import {App as AppContainer, Entry, SideBar, Content, Separator} from 'oc-react-components'; import {LogSearch} from './Search.js'; +import {LogFile} from './Providers/LogFile.js' import styles from '../css/app.css'; @@ -14,31 +16,36 @@ export class App extends Component { state = { 'entries': [], 'loading': false, - 'levels': [false, false, false, false, false] + 'levels': [false, false, false, false, false], + provider: null }; constructor () { super(); this.logProvider = new LogProvider(50); this.logProvider.on('entries', entries => { - this.setState({entries}); + if (this.state.provider === this.logProvider) { + this.setState({entries}); + } }); OCA.Search.logreader = new LogSearch(this.logProvider); this.saveLevels = _.debounce(this.logProvider.setLevels, 100); } - async componentDidMount() { + async componentDidMount () { const levels = await this.logProvider.getLevels(); - this.setState({levels}); + this.setState({levels, provider: this.logProvider}); this.logProvider.load(); } - fetchNextPage = async() => { - this.setState({loading: true}); - this.logProvider.limit += 25; - await this.logProvider.load(); - this.setState({loading: false}); - }; + fetchNextPage = _.throttle(async() => { + if (this.state.provider.hasMore) { + this.setState({loading: true}); + this.state.provider.limit += 25; + await this.state.provider.load(); + this.setState({loading: false}); + } + }, 100); setLevel (level, newState) { let levels = this.state.levels; @@ -53,6 +60,17 @@ export class App extends Component { this.saveLevels(levels); } + onLogFile = (content) => { + const logFile = new LogFile(content); + logFile.on('entries', entries => { + if (this.state.provider === logFile) { + this.setState({entries}); + } + }); + this.setState({provider: logFile, entries: []}); + logFile.load(); + }; + render () { let entries = this.state.entries.filter(entry=> { return this.state.levels[entry.level]; @@ -70,6 +88,8 @@ export class App extends Component { return ( <AppContainer appId="logreader"> <SideBar> + <Entry><LogUploader onLogFile={this.onLogFile}/></Entry> + <Separator/> {filters} </SideBar> diff --git a/js/Components/LogUploader.js b/js/Components/LogUploader.js new file mode 100644 index 0000000..de14340 --- /dev/null +++ b/js/Components/LogUploader.js @@ -0,0 +1,40 @@ +import {Component} from 'react/addons'; +import Dropzone from 'react-dropzone'; + +import style from './LogUploader.less'; + +export class LogUploader extends Component { + state = { + message: 'Load log file...' + }; + + isLog (content) { + return content[0] === '{' && content[content.length - 1] === '}'; + } + + onDrop = (files) => { + const file = files[0]; + const reader = new FileReader(); + reader.onload = (e) => { + const content = e.target.result.trim(); + if (!this.isLog(content)) { + this.setState({message: 'Invalid log file'}); + return; + } + this.props.onLogFile(content); + }; + reader.readAsText(file); + }; + + render () { + const dropStyle = { + margin: '0 -12px', + padding: '0 12px' + }; + return ( + <Dropzone style={dropStyle} onDrop={this.onDrop}> + {this.state.message} + </Dropzone> + ); + } +} diff --git a/js/Components/LogUploader.less b/js/Components/LogUploader.less new file mode 100644 index 0000000..26bc244 --- /dev/null +++ b/js/Components/LogUploader.less @@ -0,0 +1,3 @@ +.logSelect { + margin: 0 -12px; +} diff --git a/js/Providers/LogFile.js b/js/Providers/LogFile.js new file mode 100644 index 0000000..24a993b --- /dev/null +++ b/js/Providers/LogFile.js @@ -0,0 +1,16 @@ +import {LogProvider} from './LogProvider.js' + +export class LogFile extends LogProvider { + constructor (content, limit) { + super(limit); + this.content = content; + this.lines = this.content.split('\n'); + } + + async loadEntries (offset, count = 50) { + const start = this.lines.length - offset; + const end = Math.max(start - count - 2, 0); + const entries = this.lines.slice(end, start).reverse().map(JSON.parse) + return {data: entries}; + } +} diff --git a/js/Providers/LogProvider.js b/js/Providers/LogProvider.js index c199005..622a15e 100644 --- a/js/Providers/LogProvider.js +++ b/js/Providers/LogProvider.js @@ -3,7 +3,9 @@ import {EventEmitter} from 'events'; export class LogProvider extends EventEmitter { static levels = ['Debug', 'Info', 'Warning', 'Error', 'Fatal']; + fromFile = false; cachedEntries = []; + hasMore = true; constructor (limit = 50) { super(); @@ -37,10 +39,13 @@ export class LogProvider extends EventEmitter { async load () { this.loading = true; - if (this.cachedEntries.length >= this.limit) { + if (this.cachedEntries.length >= this.limit || this.fromFile || !this.hasMore) { return; } var newData = await this.loadEntries(this.cachedEntries.length, this.limit - this.cachedEntries.length); + if(newData.data.length === 0) { + this.hasMore = false; + } this.cachedEntries = this.cachedEntries.concat(newData.data); this.loading = false; this.emit('entries', this.cachedEntries); diff --git a/package.json b/package.json index 47b16cd..ddf5c3c 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,7 @@ "less-loader": "^2.2.0", "oc-react-components": "^0.1.5", "react": "^0.13.3", + "react-dropzone": "^2.1.0", "react-responsive": "0.0.8", "react-scrolla": "^0.1.0", "react-time": "^3.0.0" |