Updating UI for perf; core fixes

Adding socket options

Core changes; config management; serializing
This commit is contained in:
bluejamesbond 2016-04-09 05:09:53 -05:00
parent 4ac1a16ce4
commit b29dcb6a6c
No known key found for this signature in database
GPG key ID: 549DD3C15A71FB34
95 changed files with 107521 additions and 1520 deletions

View file

@ -1,10 +1,28 @@
{
"presets": [
"es2015",
"react"
"presets": [
"es2015",
"react"
],
"plugins": [
"transform-object-rest-spread",
"transform-runtime",
"syntax-class-properties",
"transform-export-default-name",
[
"transform-async-functions",
{
"module": "bluebird",
"method": "coroutine"
}
],
"plugins": [
"transform-object-rest-spread",
"transform-runtime"
]
[
"transform-async-to-module-method",
{
"module": "bluebird",
"method": "coroutine"
}
],
"transform-class-properties",
"syntax-async-functions"
]
}

141
.scriberc Normal file
View file

@ -0,0 +1,141 @@
{
"app": "Scribe",
"id": "instance-0",
"handleUncaughtException": true,
"reader": "reader/BasicConsole",
"expose": {
"default": [
"mongo-socket",
"bash"
],
"express": [
"express-mongo-socket",
"express-bash"
]
},
"expose/pipeline": {
"mongo-socket": [
"transform/ErrorExtractor",
"transform/ToJSON2",
"transform/FullTextSerialize",
"writer/MongoDB",
"writer/SocketIO"
],
"express-mongo-socket": [
"transform/ExpressExtractor",
"transform/ErrorExtractor",
"transform/ToJSON2",
"transform/FullTextSerialize",
"writer/MongoDB",
"writer/SocketIO"
],
"bash": [
"transform/Inspector",
"writer/DefaultConsole"
],
"express-bash": [
"transform/ExpressExtractor",
"transform/ExpressInspector",
"transform/Inspector",
"writer/DefaultConsole"
]
},
"module": {
"middleware/ExpressLogger": {
"expose": "express",
"ignore": ["(scribe)"]
},
"writer/MongoDB": {
"uri": "mongodb://localhost/scribe"
},
"writer/SocketIO": {
"port": 4000,
"options": {}
},
"transform/Inspector": {
"colors": true,
"showHidden": false,
"depth": 5,
"pre": true,
"callsite": true,
"tags": true,
"args": true,
"metrics": true
},
"router/Viewer": {
"mongoUri": "mongodb://localhost/scribe",
"basePath": "/scribe",
"username": "build",
"password": "build",
"authentication": true,
"sessionSecret": "scribe-session",
"useBodyParser": true,
"useSession": true
},
"router/Viewer/native": {
"protocol": "",
"host": "",
"port": "",
"pathname": ""
},
"router/Viewer/client": {
"background": "#222",
"socketPorts": [
4000
],
"socketOptions": {},
"queries": {
"all": {
"label": "all",
"query": {
"expose": {
"$exists": true
}
}
},
"error": {
"label": "error",
"query": {
"expose": "error"
},
"color": "red"
},
"express": {
"label": "express",
"query": {
"expose": "express"
},
"color": "blue"
},
"info": {
"label": "info",
"query": {
"expose": "info"
},
"color": "cyan"
},
"log": {
"label": "log",
"query": {
"expose": "log"
},
"color": "green"
},
"warn": {
"label": "warn",
"query": {
"expose": "warn"
},
"color": "yellow"
},
"trace": {
"label": "trace",
"query": {
"expose": "trace"
},
"color": "magenta"
}
}
}
}
}

View file

@ -104,7 +104,7 @@ module.exports = function (grunt) {
map: false,
processors: [
require('autoprefixer')({
browsers: ['Chrome > 20']
browsers: ['> 1%']
})
]
},
@ -178,7 +178,7 @@ module.exports = function (grunt) {
grunt.registerTask('styles', ['sass:dist', 'postcss:dist']);
grunt.registerTask('build', ['get-deps', 'sass:dist', 'postcss:dist', 'browserify:dist', 'copy', 'imagemin', 'babel']);
grunt.registerTask('default', 'build');
grunt.registerTask('auto-build-scripts', ['browserify:dev']);
grunt.registerTask('auto-build-styles', ['sass:dev', 'watch:sass']);
grunt.registerTask('watch-scripts', ['browserify:dev']);
grunt.registerTask('watch-styles', ['sass:dev', 'watch:sass']);
grunt.registerTask('production', ['env-force-production', 'clean', 'build', 'uglify:dist', 'env-restore']);
};

1
Procfile Normal file
View file

@ -0,0 +1 @@
web: node ./herokuapp/app.js

View file

@ -3,7 +3,7 @@
Node.js logging made simple! Online access to logs and more...
```bash
npm install git://github.com/bluejamesbond/Scribe.js.git#dev
npm install scribe@3.0.0-alpha.5
```
### Features
@ -16,19 +16,21 @@ npm install git://github.com/bluejamesbond/Scribe.js.git#dev
- Support for multithreading (clusters)
- Support for logging custom metrics i.e. `databaseResponseTime`
- ES6 and Promise support - see examples
- Aggregated timeseries of the log entries
- Range selection
- Keyword searching
### Future
- Add client-side options i.e. hide/display tags, show timings, abstract away search
- Endpoint response time graphing
- Graphing tools for custom metrics
- Support for third-party plugins i.e. data parsing and performance tracking (in progress)
- Delete logs from front-end
## Console
![](/screenshots/console-0.png)
## Web
![](/screenshots/web-panel-1.png)
![](https://www.dropbox.com/s/pgjd6eu692fen8y/Screenshot%202016-04-10%2019.52.57.png?dl=1)
## Native
![](/screenshots/native-0.png)

View file

@ -1,11 +1,13 @@
import App from './components/App.jsx'
import React from 'react'
import MongoDB from './libs/MongoDB'
import { render } from 'react-dom'
import {render} from 'react-dom'
window.Meteor = {Collection: {ObjectID: Function}};
const db = new MongoDB();
db.init()
.then(db=>window.db = db)
.then(()=>db.addCollection('Entry'))
.then(()=>render(<App />, document.getElementById('app')));
.then(db=>window.db = db)
.then(()=>db.addCollection('Entry'))
.then(()=>render(<App />, document.getElementById('app')));

View file

@ -8,6 +8,43 @@ import Sidebar from './views/Sidebar.jsx'
import ReactList from 'react-list';
import querystring from 'querystring'
class ReactListTracked extends ReactList {
constructor(...args) {
super(...args);
this._triggerTimeSeriesBound = this._triggerTimeSeries.bind(this);
this.session = null;
}
componentDidUpdate() {
super.componentDidUpdate();
this._triggerTimeSeries();
}
_triggerTimeSeries() {
const [first] = this.getVisibleRange();
if (this.props.entries[first]) {
Dispatcher.emit(Dispatcher.Events.REQUEST_TIMESERIES_DATE, this.props.entries[first].date);
}
}
componentWillUnmount() {
super.componentWillUnmount();
this.scrollParent.removeEventListener("scroll", this._triggerTimeSeriesBound);
}
componentDidMount() {
super.componentDidMount();
this.scrollParent.addEventListener("scroll", this._triggerTimeSeriesBound);
window.document.body.style.background = config.background;
}
}
class App extends Influx.Component {
constructor(...args) {
super(...args);
@ -24,6 +61,12 @@ class App extends Influx.Component {
}
_onEntryStoreUpdated() {
const session = EntryStore.getSessionId();
if (this.session !== session) {
this.lastLength = null;
this.session = session;
}
this.setState({entries: EntryStore.getSearchResults()});
}
@ -31,7 +74,13 @@ class App extends Influx.Component {
Dispatcher.emit(Dispatcher.Events.REQUEST_INIT_SOCKET);
}
componentDidUpdate() {
const {entries} = this.state;
}
componentDidMount() {
super.componentDidMount();
Dispatcher.emit(Dispatcher.Events.REQUEST_INIT_DATABASE);
}
@ -41,25 +90,33 @@ class App extends Influx.Component {
const maxLineChars = String(length).length + 3;
const rowRenderer = (i, key) => {
return <Entry key={key} lines={length} line={length - i} maxLineChars={maxLineChars}
entry={this.state.entries[i]}/>
const entry = this.state.entries[i];
if (i + 1 === length && this.lastLength !== length) {
Dispatcher.emit(Dispatcher.Events.REQUEST_GROW_SEARCH);
Dispatcher.emit(Dispatcher.Events.REQUEST_TIMESERIES_DATE, entry.date);
this.lastLength = length;
}
return <Entry key={key} lines={length} line={i + 1} maxLineChars={maxLineChars}
entry={entry}/>
};
return (
<div className="full flex">
<Sidebar hide={this.state.hideSidebar}/>
<div className='full box'>
<div className='full-abs'>
<div className="full flex vertical" style={{overflow:'hidden'}}>
<Header hide={this.state.hideHeader}/>
<div style={{overflow:'scroll',overflowX:'hidden',paddingTop:10}}>
<ReactList useTranslate3d={true} length={length} itemRenderer={rowRenderer}/>
</div>
<div className="full flex">
<Sidebar hide={this.state.hideSidebar}/>
<div className='full box'>
<div className='full-abs'>
<div className="full flex vertical" style={{overflow:'hidden'}}>
<Header hide={this.state.hideHeader}/>
<div style={{overflow:'scroll',overflowX:'hidden',paddingTop:10}}>
<ReactListTracked entries={entries} ref='list' useTranslate3d={true} length={length}
itemRenderer={rowRenderer}/>
</div>
</div>
</div>
</div>
</div>
);
}
}

View file

@ -0,0 +1,58 @@
import React from 'react';
import {ifcat} from '../../libs/utils';
class Checkbox extends React.Component {
static propTypes = {
defaultChecked: React.PropTypes.bool,
checked: React.PropTypes.bool,
toggle: React.PropTypes.bool,
style: React.PropTypes.object,
true: React.PropTypes.string,
false: React.PropTypes.string,
onChange: React.PropTypes.func
};
static defaultProps = {
defaultChecked: false,
checked: false,
style: {},
true: 'on',
false: 'off',
toggle: false,
onChange: () => 0
};
constructor(...args) {
super(...args);
this.state = {checked: this.props.defaultChecked || this.props.checked};
}
componentWillReceiveProps(props) {
if (props.checked !== this.state.checked) {
this.setState({checked: props.checked});
}
}
handleChecked(e) {
if (this.props.onChange) {
e.target.checked = !this.state.checked;
this.props.onChange(e);
}
}
render() {
return (
<div
className={ifcat('checkbox', {toggle: this.props.toggle, checked: this.state.checked})}
style={this.props.style}
onClick={e => this.handleChecked(e)}>
{this.props.toggle ? <div
className='label'>{this.state.checked ? this.props.true : this.props.false}</div> : null }
<div className='check'></div>
</div>
);
}
}
export default Checkbox;

View file

@ -1,29 +1,51 @@
import React from 'react'
import Influx from 'react-influx'
import {ifcat} from '../../libs/utils'
import EntryStore from '../../stores/EntryStore';
import Dispatcher from '../../dispatchers/Dispatcher';
import ObjectInspector from 'react-object-inspector';
import _ from 'underscore'
import Checkbox from './Checkbox.jsx';
export default class Entry extends React.Component {
export default class Entry extends Influx.Component {
constructor(...args) {
super(...args);
this.state = {visible: false, width: window.innerWidth};
this.state = {visible: false, width: window.innerWidth, selected: EntryStore.isSelected(this.props.entry._id)};
this._onWindowResize = this._onWindowResize.bind(this);
}
componentWillUnmount() {
window.removeEventListener("resize", this._onWindowResize)
super.componentWillUnmount();
window.removeEventListener("resize", this._onWindowResize);
}
componentDidMount() {
super.componentDidMount();
window.addEventListener("resize", this._onWindowResize, false)
}
componentWillReceiveProps(nextProps) {
this.setState({selected: EntryStore.isSelected(nextProps.entry._id)});
}
_onWindowResize() {
this.setState({width: window.innerWidth});
}
getListeners() {
return [
[EntryStore, EntryStore.Events.UPDATED_SELECTED, this._onUpdateSelected]
]
}
_onUpdateSelected() {
this.setState({selected: EntryStore.isSelected(this.props.entry._id)});
}
_inspectPre(persistent) {
return <span className="pre yellow">{`${persistent.app}-${persistent.id}`}</span>;
return <span className="pre">{`${persistent.app}-${persistent.id}`}</span>;
}
_inspectTags(tags, persistent) {
@ -35,7 +57,7 @@ export default class Entry extends React.Component {
_inspectExpose(expose) {
return <span className={ifcat('tag',{
fill: true,
bgRed:expose == 'error',
bgRed: expose == 'error',
//bgBlue: expose == 'express',
//bgGreen: expose == 'log'
})}>{expose}</span>;
@ -47,11 +69,11 @@ export default class Entry extends React.Component {
_inspectMetrics(entry) {
return (
<span>
<span>
{_.map(entry.transient.metrics, (value, key)=> {
value = typeof value === 'number' ? value.toFixed(3) : value;
return (
<span key={key}>
<span key={key}>
<span className='tag fill bgPurple'>{key}</span>
<span className='tag green'>{value}</span>
</span>
@ -68,12 +90,12 @@ export default class Entry extends React.Component {
const {entry} = this.props;
return (
<div className="raw inspector flex">
<span className="line" dangerouslySetInnerHTML={{__html:preSpace}}/>
<div className="scroll scroll-x">
<ObjectInspector data={ entry } initialExpandedPaths={["*"]}/>
</div>
<div className="raw inspector flex">
<span className="line" dangerouslySetInnerHTML={{__html:preSpace}}/>
<div className="scroll scroll-x">
<ObjectInspector data={ entry } initialExpandedPaths={["*"]}/>
</div>
</div>
);
}
@ -83,7 +105,7 @@ export default class Entry extends React.Component {
if (entry.expose === 'express') {
const express = entry.args[0];
args = (
<span>
<span>
<span className='tag fill bgGreen'>{express.method}</span>
<span className='gray'>{express.originalUrl}</span>
<span className="green" style={{margin:'0 4px'}}>{express.status} - {express.contentLength} </span>
@ -104,13 +126,21 @@ export default class Entry extends React.Component {
_inspectCallSite(entry) {
let site = entry.transient.callsite;
let tooltip;
if (entry.expose === 'express') {
const express = entry.args[0];
site = `${express.ip}`
site = `${express.ip}`;
tooltip = 'remote ip';
} else {
site = `${site.file.substr(site.file.lastIndexOf('/') + 1)}:${site.line}`;
tooltip = site.func || 'anonymous';
}
return <span className={ifcat('tag',{fill: true})}>{site}</span>;
return (
<span className={ifcat('tag tooltip',{fill: true})}>
{site}
<span className='tooltiptext tooltip-top'>{tooltip}</span>
</span>
);
}
render() {
@ -118,47 +148,55 @@ export default class Entry extends React.Component {
const lineNumber = new Array(this.props.maxLineChars - String(this.props.line).length).join('&nbsp;') + this.props.line;
const preSpace = new Array(this.props.maxLineChars).join('&nbsp;');
const isPushed = entry._pushed;
if (this.state.width < 1280) {
return (
<div className="entry">
<div className="pretty flex">
<span className="line" dangerouslySetInnerHTML={{__html:lineNumber}}/>
<span>{this._inspectPre(entry.persistent)}</span>
<span>{this._inspectTags(entry.persistent.tags, true)}</span>
<span>{this._inspectTags(entry.transient.tags)}</span>
<span>{this._inspectMetrics(entry)}</span>
<span className="box"/>
<span>{this._inspectExpose(entry.expose)}</span>
<span>{this._inspectCallSite(entry)}</span>
<span className={ifcat("expand-right gray", {rotate:this.state.visible})}
onClick={this._handleClick.bind(this)}></span>
</div>
<div className="flex">
<span className="line" dangerouslySetInnerHTML={{__html:preSpace}}/>
<span className="white medium inspector box scroll scroll-x">{this._inspectArgs(entry)}</span>
</div>
{this._inspectRaw(preSpace) }
<div className="entry">
<div className="pretty flex">
<span className="line" dangerouslySetInnerHTML={{__html:lineNumber}}/>
<Checkbox type='checkbox' checked={this.state.selected} style={{marginBottom: 4}}
onChange={e => Dispatcher.emit(Dispatcher.Events.REQUEST_SELECT_ENTRY, entry._id, e.target.checked)}/>
<span>{this._inspectPre(entry.persistent)}</span>
<span>{this._inspectTags(entry.persistent.tags, true)}</span>
<span>{this._inspectTags(entry.transient.tags, false, isPushed)}</span>
<span>{this._inspectMetrics(entry)}</span>
<span className="box"/>
<span>{this._inspectExpose(entry.expose)}</span>
<span>{this._inspectCallSite(entry)}</span>
{ isPushed ? <span><span className='tag fill new-entry'>new</span></span> : null }
<span className={ifcat("expand-right gray", {rotate:this.state.visible})}
onClick={this._handleClick.bind(this)}></span>
</div>
<div className="flex">
<span className="line" dangerouslySetInnerHTML={{__html:preSpace}}/>
<span className="white medium inspector box scroll scroll-x"
style={{marginLeft: 22}}>{this._inspectArgs(entry)}</span>
</div>
{ this._inspectRaw(preSpace) }
</div>
);
}
return (
<div className="entry">
<div className="flex">
<span className="line" dangerouslySetInnerHTML={{__html:lineNumber}}/>
<span>{this._inspectPre(entry.persistent)}</span>
<span>{this._inspectTags(entry.persistent.tags, true)}</span>
<span>{this._inspectTags(entry.transient.tags)}</span>
<span>{this._inspectMetrics(entry)}</span>
<span className="white medium inspector box scroll scroll-x">{this._inspectArgs(entry)}</span>
<span>{this._inspectExpose(entry.expose)}</span>
<span>{this._inspectCallSite(entry)}</span>
<span className={ifcat("expand-right gray", {rotate:this.state.visible})}
onClick={this._handleClick.bind(this)}></span>
</div>
{this._inspectRaw(preSpace) }
<div className="entry">
<div className="flex">
<span className="line" dangerouslySetInnerHTML={{__html:lineNumber}}/>
<Checkbox type='checkbox' checked={this.state.selected}
onChange={e => Dispatcher.emit(Dispatcher.Events.REQUEST_SELECT_ENTRY, entry._id, e.target.checked)}/>
<span>{this._inspectPre(entry.persistent)}</span>
<span>{this._inspectTags(entry.persistent.tags, true)}</span>
<span>{this._inspectTags(entry.transient.tags, false, isPushed)}</span>
<span>{this._inspectMetrics(entry)}</span>
<span className="white medium inspector box scroll scroll-x">{this._inspectArgs(entry)}</span>
<span>{this._inspectExpose(entry.expose)}</span>
<span>{this._inspectCallSite(entry)}</span>
{ isPushed ? <span><span className='tag fill new-entry'>new</span></span> : null }
<span className={ifcat("expand-right gray", {rotate:this.state.visible})}
onClick={this._handleClick.bind(this)}></span>
</div>
{this._inspectRaw(preSpace) }
</div>
);
}
}

View file

@ -4,6 +4,10 @@ import {ifcat} from '../../libs/utils'
import EntryStore from '../../stores/EntryStore'
import Dispatcher from '../../dispatchers/Dispatcher'
import Spinner from 'react-spinkit'
import _ from 'underscore';
import moment from 'moment';
import TimeSeries from './TimeSeries.jsx';
import DatePicker from 'react-datepicker'
import AceEditor from 'react-ace';
import brace from 'brace'
@ -13,22 +17,54 @@ import 'brace/theme/monokai';
export default class Header extends Influx.Component {
constructor(...args) {
super(...args);
this.state = {search: {}, searchEnabled: false};
this.state = {
search: {
period: {}
},
selected: [],
searchEnabled: false,
startDate: moment(),
endDate: moment()
};
this.search = {};
this.text = null;
this.expanded = true;
this.isTyping = false;
}
getListeners() {
return [
[EntryStore, EntryStore.Events.UPDATED, this._onEntryStoreUpdated]
[EntryStore, EntryStore.Events.UPDATED, this._onEntryStoreUpdated],
[EntryStore, EntryStore.Events.UPDATED_SELECTED, this._onEntryStoreUpdatedSelected]
]
}
_onEntryStoreUpdated() {
const search = {query: EntryStore.getSearchQuery(), options: EntryStore.getSearchOptions()};
this.setState({search, searchEnabled: true});
const search = {
query: EntryStore.getSearchQuery(),
options: EntryStore.getSearchOptions(),
period: EntryStore.getSearchPeriod(),
text: EntryStore.getSessionText()
};
if (!this.isTyping) {
this.refs.input.value = search.text || '';
}
const startDate = moment(search.period.$gt);
const endDate = moment(search.period.$lt).subtract(1, 'days');
this.setState({search, searchEnabled: true, startDate, endDate});
this.search = JSON.stringify(search);
}
_onEntryStoreUpdatedSelected() {
const selected = EntryStore.getSelected();
this.setState({selected});
this.selected = JSON.stringify(selected);
}
_handleChange(key, data) {
if (!this.state.searchEnabled) {
return;
@ -44,16 +80,148 @@ export default class Header extends Influx.Component {
_handleSearch() {
try {
const {query,options} = this.state.search;
Dispatcher.emit(Dispatcher.Events.REQUEST_ENTRY_SEARCH, query, options, true);
Dispatcher.emit(Dispatcher.Events.REQUEST_ENTRY_SEARCH, query, options);
} catch (e) {
console.error(e);
}
}
_triggerTextSearch() {
try {
const {query, options, period} = this.state.search;
Dispatcher.emit(Dispatcher.Events.REQUEST_ENTRY_SEARCH, query, options, period, this.text);
} catch (e) {
console.error(e);
}
}
_handleTextSearch(text) {
clearTimeout(this.triggerTid);
this.text = text;
this.isTyping = true;
this.triggerTid = setTimeout(() => this._triggerTextSearch(), 400);
}
_handleDateChange({ startDate, endDate }) {
startDate = startDate || this.state.startDate;
endDate = endDate || this.state.endDate;
if (startDate.isAfter(endDate)) {
var temp = startDate;
startDate = endDate;
endDate = temp
}
// if (moment.duration(endDate.diff(startDate)).asDays() > 5) {
// startDate = moment(endDate).subtract(5, 'days');
// }
Dispatcher.emit(Dispatcher.Events.REQUEST_PERIOD, {
$lt: endDate.add(1, 'days').startOf('day').toISOString(),
$gt: startDate.startOf('day').toISOString()
});
this.setState({startDate, endDate})
}
_handleChangeStartDate(startDate) {
this._handleDateChange({startDate})
}
_handleChangeEndDate(endDate) {
this._handleDateChange({endDate})
}
componentDidMount() {
super.componentDidMount();
this._crushHeader();
}
_growHeader() {
if (this.expanded) return;
this.expanded = true;
this.refs.root.style.minHeight = '245px';
this.refs.root.style.maxHeight = '245px';
this.refs.inside.style.minHeight = '125px';
this.refs.inside.style.maxHeight = '125px';
this.refs.inside.style.height = '125px';
this.refs.arrow.classList.remove('rotate-down');
clearTimeout(this.tid);
this.tid = setTimeout(() => {
this.refs.query.classList.remove('visibility-hidden');
this.refs.select.classList.remove('visibility-hidden');
}, 450);
}
_crushHeader() {
if (!this.expanded) return;
this.expanded = false;
this.refs.query.classList.add('visibility-hidden');
this.refs.select.classList.add('visibility-hidden');
this.refs.arrow.classList.add('rotate-down');
clearTimeout(this.tid);
this.tid = setTimeout(() => {
this.refs.root.style.minHeight = '170px';
this.refs.root.style.maxHeight = '170px';
this.refs.inside.style.minHeight = '50px';
this.refs.inside.style.maxHeight = '50px';
this.refs.inside.style.height = '50px';
}, 450);
}
_toggleCrush() {
if (this.expanded) {
this._crushHeader();
} else {
this._growHeader();
}
}
render() {
const querystring = EntryStore.getQueryString({_id: {$in: this.state.selected}}, []);
return (
<div className={ifcat('header', {hide:this.props.hide})} style={{padding:13}}>
<div className="flex">
<div style={{minHeight: 245, overflow: 'hidden'}} ref='root' className='animate-height'>
<div className='flex' style={{position: 'relative'}}>
<input ref='input' style={{paddingRight: 40}} className='big-input' placeholder='Starting typing...'
onChange={e => this._handleTextSearch(e.target.value)}/>
<span ref='arrow' className='animate-transform arrow-expand icon arrow' onClick={() => this._toggleCrush()}/>
</div>
<div ref='inside' className={ifcat('header animate-height', {hide:this.props.hide})} style={{padding:13}}>
<div className="flex " ref="range">
<div className="box">
<div className='field flex'>
<div className="name box">range</div>
<div className="input box">
<div className='date-holder'>
<DatePicker
selected={this.state.startDate}
startDate={this.state.startDate}
endDate={this.state.endDate}
onChange={a => this._handleChangeStartDate(a)}/>
<span className='date-to'>to</span>
<DatePicker
selected={this.state.endDate}
startDate={this.state.startDate}
endDate={this.state.endDate}
onChange={a => this._handleChangeEndDate(a)}/>
</div>
</div>
</div>
</div>
</div>
<div className="flex animate-visibility" style={{marginTop: 10}} ref="query">
<div className="box" style={{marginRight:13}}>
<div className='field flex'>
<div className="name box">query</div>
@ -70,7 +238,7 @@ export default class Header extends Influx.Component {
<div className="name box">options</div>
<div className="input box">
<AceEditor width="100%" showPrintMargin={false} showGutter={false} mode="json" height="100%"
highlightActiveLine={false} minLines={1} maxLines={1} name="sort" theme="monokai"
highlightActiveLine={false} minLines={1} maxLines={1} name="options" theme="monokai"
onChange={this._handleChange.bind(this, 'options')}
value={JSON.stringify(this.state.search.options)} editorProps={{$blockScrolling: true}}/>
</div>
@ -83,7 +251,36 @@ export default class Header extends Influx.Component {
</div>
</div>
</div>
<div className="flex animate-visibility" style={{marginTop: 10}} ref="select">
<div className="box" style={{marginRight:13}}>
<div className='field flex readonly'>
<div className="name box">SELECT</div>
<div className="input box">
<AceEditor width="100%" showPrintMargin={false} showGutter={false} mode="json" height="100%"
highlightActiveLine={false} minLines={1} maxLines={1} name="selected" theme="monokai"
readOnly={true} value={JSON.stringify(this.state.selected)}
editorProps={{$blockScrolling: true}}/>
</div>
</div>
</div>
<div className="box" style={{maxWidth:100,position:'relative', marginRight:13}}>
<a className='button' target="_blank" style={{background: '#4CAF50'}}
href={`#${querystring}`}>PERMALINK</a>
</div>
<div className="box" style={{maxWidth:100,position:'relative', marginRight:13}}>
<div className='button' onClick={() => Dispatcher.emit(Dispatcher.Events.REQUEST_SELECT_ALL_ENTRIES)}>
SELECT ALL
</div>
</div>
<div className="box" style={{maxWidth:100,position:'relative'}}>
<div className='button' onClick={() => Dispatcher.emit(Dispatcher.Events.REQUEST_SELECT_CLEAR_ENTRIES)}>
CLEAR
</div>
</div>
</div>
</div>
<TimeSeries />
</div>
);
}
}

View file

@ -11,7 +11,7 @@ export default class Sidebar extends Influx.Component {
constructor(...args) {
super(...args);
this.state = {date: moment().startOf('day')};
this.state = {};
}
getListeners() {
@ -21,58 +21,49 @@ export default class Sidebar extends Influx.Component {
]
}
_onEntryStoreDatabaseReady() {
const exposed = Object.keys(config.exposed);
this._selectExpose(exposed[0], config.exposed[exposed[0]]);
_onEntryStoreDatabaseReady(selected) {
const exposed = Object.keys(config.queries);
if (!selected) {
this._selectExpose(exposed[0], config.queries[exposed[0]]);
}
}
_onDispatcherRequestEntrySearch(query, sort) {
const {expose} = query;
for (const [k, exposed] of Object.entries(config.exposed)) {
if (_.isEqual(exposed.query.expose, expose)) {
return this.setState({label: exposed.label});
const _query = JSON.parse(JSON.stringify(query));
delete _query.serialized;
for (const [k, exposed] of Object.entries(config.queries)) {
if (_.isEqual(_query, exposed.query)) {
return this.setState({i: k});
}
}
this.setState({label: undefined});
this.setState({i: undefined});
}
_selectExpose(i, expose, date = this.state.date) {
_selectExpose(i, expose) {
this.setState({i});
Dispatcher.emit(Dispatcher.Events.REQUEST_ENTRY_SEARCH,
Object.assign({}, expose.query, {date: this._getRange(date)}));
}
_getRange(date = moment()) {
const today = moment(date).startOf('day');
const tomorrow = moment(today).add(1, 'days');
return {$gte: today.toISOString(), $lt: tomorrow.toISOString()};
}
_selectDate(date) {
const query = EntryStore.getSearchQuery();
this.setState({date});
Dispatcher.emit(Dispatcher.Events.REQUEST_ENTRY_SEARCH,
Object.assign({}, query, {date: this._getRange(date)}));
Dispatcher.emit(Dispatcher.Events.REQUEST_ENTRY_SEARCH, Object.assign({}, expose.query));
}
render() {
const exposed = _.map(config.exposed, (expose, i)=> {
return <div key={i} className={ifcat('item', {selected:this.state.i === i})}
const exposed = _.map(config.queries, (expose, i)=> {
return <div key={i} className={ifcat('item', {selected: this.state.i === i})}
onClick={this._selectExpose.bind(this, i, expose, this.state.date)}>{expose.label}</div>;
});
exposed.push(<div key={'__custom__'} style={{cursor: 'default'}}
className={ifcat('item', {selected: !this.state.i})}>USER-DEFINED</div>);
return (
<div className={ifcat('sidebar box',{hide:this.props.hide})}>
<div className="group">
<div className="title">Exposed <DatePicker onChange={this._selectDate.bind(this)}
selected={this.state.date}/>
</div>
{exposed}
</div>
<div className={ifcat('sidebar box',{hide:this.props.hide})}>
<div className="group">
<a className="title" href={`${window.location.origin}${window.location.pathname}`}>Scribe.js</a>
{exposed}
</div>
</div>
);
}
}

View file

@ -0,0 +1,135 @@
import React from 'react'
import Influx from 'react-influx'
import {ifcat} from '../../libs/utils'
import EntryStore from '../../stores/EntryStore'
import Dispatcher from '../../dispatchers/Dispatcher'
import Spinner from 'react-spinkit'
import _ from 'underscore';
import moment from 'moment';
const GROWTH = 1;
export default class TimeSeries extends Influx.Component {
constructor(...args) {
super(...args);
this.state = {data: [], series: []};
}
getListeners() {
return [
[EntryStore, EntryStore.Events.UPDATED_TIMESERIES, this._onEntryStoreUpdatedTimeSeries],
[Dispatcher, Dispatcher.Events.REQUEST_TIMESERIES_DATE, this._onDispatcherRequestTimeSeriesDate]
]
}
_onDispatcherRequestTimeSeriesDate(date) {
date = moment(date);
const {series} = this.state;
for (var i = 0; i < series.length; i++) {
const data = series[i];
// console.log(data.date.toDate(), date.toDate());
if (data.date.diff(date) < 0) {
Array.prototype.forEach.call(document.getElementsByClassName('red-selected'), a => a.classList.remove('red-selected'));
this.refs[data.ref].classList.add('red-selected');
return;
}
}
}
_onEntryStoreUpdatedTimeSeries() {
const period = EntryStore.getSearchPeriod();
const aggregations = EntryStore.getSearchTimeSeries();
const data = _.groupBy(aggregations, a => `${a._id.hour.day}_${a._id.hour.hour}`);
const end = moment(period.$lt);
const series = [];
const exposers = Object.keys(_.indexBy(aggregations, a => a._id.expose));
let start = moment(period.$gt);
let max = 0;
for (var i in data) {
const exposed = {};
for (var k = 0; k < data[i].length; k++) {
const count = data[i][k].number;
exposed[data[i][k]._id.expose] = count;
max = Math.max(max, count);
}
data[i] = exposed;
}
let count = 0;
const offset = new Date().getTimezoneOffset() * 60 * 1000;
while (end.diff(start) > 0) {
const startOffset = moment(start.toDate().getTime() + offset);
const day = startOffset.date();
const hour = startOffset.hour();
const key = `${day}_${hour}`;
series.push({date: moment(start), ref: `pos-${count++}`, count: data[key] || {}});
start = start.add(1, 'hour');
}
var crushSeries = [];
var groupSize = parseInt(series.length / 24);
for (var i = 0; i < series.length; i += groupSize) {
const start = series[i];
for (var k = i + 1; k < i + groupSize; k++) {
const curr = series[k];
for (var e = 0; e < exposers.length; e++) {
const ee = exposers[e];
start.count[ee] = start.count[ee] || 0;
start.count[ee] += (curr.count[ee] || 0)
max = Math.max(start.count[ee], max);
}
}
crushSeries.push(start);
}
this.setState({series: crushSeries.reverse(), exposers, max});
}
render() {
const maxHeight = 80;
const {series, exposers, max} = this.state;
const elems = series.map((data, i) => {
const exposed = exposers.map(expose => {
const count = data.count[expose] || 0;
const height = Math.min(count / max * maxHeight * GROWTH, maxHeight);
return (
<div key={expose}
className={'box graph bar tooltip ' + expose}
style={{ top: `${maxHeight - height}px`, height: `${height}px`}}>
<span className='tooltiptext tooltip-top'>{`${expose}: ${count}`}</span>
</div>
);
});
return (
<div key={i} ref={data.ref} className='box graph ggroup'>
<div className='flex full gg'>
{exposed}
<div className='time-series-label'>{data.date.format('M/D hA')}</div>
</div>
</div>
);
});
return (
<div className='flex'
style={{height: maxHeight, width: '100%', position: 'relative', background: 'rgba(0,0,0,0.3)'}}>
{elems}
</div>
)
}
}

View file

@ -2,10 +2,16 @@ import Influx from 'react-influx'
import keyMirror from 'keymirror'
const Events = keyMirror({
REQUEST_INIT_DATABASE: null,
REQUEST_INIT_SOCKET: null,
REQUEST_ENTRY_SEARCH: null
});
REQUEST_GROW_SEARCH: null,
REQUEST_INIT_DATABASE: null,
REQUEST_INIT_SOCKET: null,
REQUEST_ENTRY_SEARCH: null,
REQUEST_SELECT_ENTRY: null,
REQUEST_SELECT_ALL_ENTRIES: null,
REQUEST_SELECT_CLEAR_ENTRIES: null,
REQUEST_TIMESERIES_DATE: null,
REQUEST_PERIOD: null
});
class Dispatcher extends Influx.Dispatcher {
// override as needed

View file

@ -23,12 +23,18 @@ function JQueryHttpClient(method, url, params, data, success, error) {
req.end((err, res) => {
if (err) return error(err);
return success(res.body.docs || []);
let docs = [];
try {
docs = JSON.parse(res.text);
} catch (e) {
// ignore
}
return success(docs);
}).on('progress', e => console.log('Percentage done: ', e.percent));
}
export default class MongoDB {
constructor(url = 'rest/', namespace = 'scribe', client = 'webapp') {
constructor(url = 'rest/db/', namespace = 'scribe', client = 'webapp') {
this.url = url;
this.namespace = namespace;
this.client = client;

View file

@ -2,55 +2,124 @@ import Influx from 'react-influx'
import keyMirror from 'keymirror'
import Dispatcher from '../dispatchers/Dispatcher'
import * as JSON2 from '../../../src/libs/JSON2'
import moment from 'moment';
import _ from 'underscore';
import request from 'superagent'
const OVERFLOW = 200;
const Events = keyMirror({
DATABASE_READY: null,
SOCKET_READY: null,
UPDATED: null
});
DATABASE_READY: null,
SOCKET_READY: null,
UPDATED: null,
UPDATED_SELECTED: null,
UPDATED_TIMESERIES: null
});
class EntryStore extends Influx.Store {
constructor(...args) {
super(Dispatcher/*, additional dispatchers you have */);
this.data = {entries: [], search: [], history: {}, options: {sort: {date: -1}}};
this.data = Object.assign(
{
entries: [], selected: {},
options: {sort: {date: -1}, limit: OVERFLOW},
period: {
$gt: moment().startOf('day').toISOString(),
$lt: moment().add(1, 'days').startOf('day').toISOString()
}
}, this.fromQueryString(window.location.hash.substr(1)));
this.setMaxListeners(Number.MAX_SAFE_INTEGER);
}
getSearchResults() {
return this.data.search;
return this.data.entries;
}
getDispatcherListeners() {
return [
[Dispatcher, Dispatcher.Events.REQUEST_INIT_DATABASE, this._onDispatcherRequestInitDatabase],
[Dispatcher, Dispatcher.Events.REQUEST_INIT_SOCKET, this._onDispatcherRequestInitSocket],
[Dispatcher, Dispatcher.Events.REQUEST_ENTRY_SEARCH, this._onDispatcherRequestEntrySearch]
[Dispatcher, Dispatcher.Events.REQUEST_PERIOD, this._onDispatcherRequestPeriod],
[Dispatcher, Dispatcher.Events.REQUEST_SELECT_ENTRY, this._onDispatcherRequestSelectEntry],
[Dispatcher, Dispatcher.Events.REQUEST_SELECT_ALL_ENTRIES, this._onDispatcherRequestSelectAllEntry],
[Dispatcher, Dispatcher.Events.REQUEST_SELECT_CLEAR_ENTRIES, this._onDispatcherRequestClearEntries],
[Dispatcher, Dispatcher.Events.REQUEST_ENTRY_SEARCH, this._onDispatcherRequestEntrySearch],
[Dispatcher, Dispatcher.Events.REQUEST_GROW_SEARCH, this._onDispatcherRequestGrowSearch]
]
}
_onDispatcherRequestInitDatabase() {
db.Entry.find({}).fetch(()=>this.emit(Events.DATABASE_READY), ()=>this.emit(Events.DATABASE_READY));
const presearch = Object.keys(this.data.query || {}).length;
this.emit(Events.DATABASE_READY, presearch);
if (presearch) {
Dispatcher.emit(Dispatcher.Events.REQUEST_ENTRY_SEARCH, this.data.query, this.data.options, this.data.period);
}
}
_onDispatcherRequestSelectEntry(id, selected) {
if (selected) {
this.data.selected[id] = true;
} else {
delete this.data.selected[id];
}
this.emit(Events.UPDATED_SELECTED);
this.emit(Events.UPDATED_SELECTED + id, true);
}
isSelected(id) {
return !!this.data.selected[id];
}
getSelected() {
return Object.keys(this.data.selected);
}
_onDispatcherRequestSelectAllEntry() {
const plucked = _.pluck(this.data.entries, '_id');
plucked.forEach(a => this.data.selected[a] = true);
this.emit(Events.UPDATED_SELECTED);
plucked.forEach(id => this.emit(Events.UPDATED_SELECTED + id, true));
}
_onDispatcherRequestClearEntries() {
const keys = Object.keys(this.data.selected);
this.data.selected = {};
this.emit(Events.UPDATED_SELECTED);
keys.forEach(id => this.emit(Events.UPDATED_SELECTED + id, false));
}
_onDispatcherRequestInitSocket() {
const url = window.location;
(config.socketPorts || []).map(port => {
this.socket = io(`${url.protocol}//${url.hostname}:${port}`);
this.socket.on('connection', function (socket) {
console.log('sup!!!');
});
this.socket.on('data', data => {
console.log(data);
db.localDb.Entry.upsert(data, () => {
this.searchEntries(this.data.query, this.getSearchOptions());
});
data._pushed = true;
db.localDb.Entry.upsert(data, () => this.refreshSession());
});
});
this.emit(Events.SOCKET_READY);
}
_onDispatcherRequestEntrySearch(query, options) {
this.searchEntries(query, options);
_onDispatcherRequestEntrySearch(...args) {
this.startSession(...args);
}
_onDispatcherRequestGrowSearch() {
this.fetchSession();
}
_onDispatcherRequestPeriod(period) {
this.setSessionPeriod(period);
}
getSearchTimeSeries() {
return this.data.timeseries;
}
getSearchQuery() {
@ -65,25 +134,124 @@ class EntryStore extends Influx.Store {
setTimeout(() => super.emit(...args), 0);
}
searchEntries(query = {}, options = this.data.options, remote = false) {
this.data.query = query;
this.data.options = options;
addEntries(entries, pre) {
this.data.entries = pre ? [...entries, ...this.data.entries] : [...this.data.entries, ...entries];
}
const searchLocal = ()=> {
db.localDb.Entry.find(query, Object.assign({}, options)).fetch(entries => {
this.data.search = entries.map(e => JSON2.parse(JSON.stringify(e)));
fromQueryString(string) {
try {
return JSON.parse(string);
} catch (e) {
return {};
}
}
getQueryString(query = this.data.query, select = this.data.select) {
return JSON.stringify({
query: query,
options: this.data.options,
period: this.data.period,
text: this.data.text,
select: select
});
}
getSessionDBQuery(date = this.data.period) {
return Object.assign({}, Object.assign({}, this.data.query, this.data.text ? {
serialized: {
$regex: `.*${this.data.text}.*`,
$options: 'i'
}
} : {}), {date});
}
getSessionDBOptionsQuery(options = this.data.options) {
return Object.assign({}, this.data.options, options);
}
startSession(query = this.data.query, options = this.data.options, period = this.data.period, text = this.data.text) {
this.data.entries = [];
this.data.options = options;
this.data.query = query;
this.data.finished = false;
this.data.timeseries = [];
this.data.period = period;
this.data.text = text;
this.data.session = Date.now();
db.Entry.find(this.getSessionDBQuery(), this.getSessionDBOptionsQuery({skip: 0}))
.fetch(entries => {
this.data.entries = entries;
this.emit(Events.UPDATED);
});
};
const dateKey = JSON.stringify(query.date);
remote = remote || !this.data.history[dateKey];
if (remote) {
this.data.history[dateKey] = true;
db.Entry.find({date: query.date}, Object.assign({}, options)).fetch(searchLocal);
} else {
searchLocal();
request
.post('rest/timeseries')
.send(this.getSessionDBQuery())
.end((err, res) => {
this.data.timeseries = res.body || [];
this.emit(Events.UPDATED_TIMESERIES);
});
window.location.hash = this.getQueryString();
}
getSearchPeriod() {
return this.data.period;
}
getSessionText() {
return this.data.text || '';
}
setSessionText(text) {
this.data.text = text;
this.startSession(); // restart
}
setSessionPeriod(period) {
this.data.period = period;
this.startSession(); // restart
}
getSessionId() {
return this.data.session;
}
refreshSession() {
db.localDb.Entry
.find(this.getSessionDBQuery(), this.getSessionDBOptionsQuery({limit: null, skip: 0}))
.fetch(entries => {
this.data.entries = entries;
this.emit(Events.UPDATED);
});
}
fetchSession(up) {
const {options, entries, query} = this.data;
const entry = entries[entries.length - 1];
if (entry) {
const periodCpy = JSON.parse(JSON.stringify(this.data.period));
if (up) {
delete periodCpy.$gte;
delete periodCpy.$gt;
periodCpy.$gte = entry.date;
} else {
delete periodCpy.$lte;
delete periodCpy.$lt;
periodCpy.$lte = entry.date;
}
db
.Entry
.find(this.getSessionDBQuery(periodCpy), this.getSessionDBOptionsQuery())
.fetch(() => {
this.refreshSession();
});
}
}
}

144
deps.json
View file

@ -1,4 +1,14 @@
[
{
"department": "kessler",
"relatedTo": "stuff",
"name": "JSONStream",
"licensePeriod": "perpetual",
"material": "material",
"licenseType": "(MIT OR Apache-2.0)",
"link": "git://github.com/dominictarr/JSONStream.git",
"comment": "1.1.1"
},
{
"department": "kessler",
"relatedTo": "stuff",
@ -9,6 +19,116 @@
"link": "git+https://github.com/caolan/async.git",
"comment": "1.5.2"
},
{
"department": "kessler",
"relatedTo": "stuff",
"name": "babel-plugin-syntax-async-functions",
"licensePeriod": "perpetual",
"material": "material",
"licenseType": "MIT",
"link": "https://github.com/babel/babel/tree/master/packages/babel-plugin-syntax-async-functions",
"comment": "6.5.0"
},
{
"department": "kessler",
"relatedTo": "stuff",
"name": "babel-plugin-syntax-class-properties",
"licensePeriod": "perpetual",
"material": "material",
"licenseType": "MIT",
"link": "https://github.com/babel/babel/tree/master/packages/babel-plugin-syntax-class-properties",
"comment": "6.5.0"
},
{
"department": "kessler",
"relatedTo": "stuff",
"name": "babel-plugin-transform-async-functions",
"licensePeriod": "perpetual",
"material": "material",
"licenseType": "MIT",
"link": "https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-async-functions",
"comment": "6.5.0"
},
{
"department": "kessler",
"relatedTo": "stuff",
"name": "babel-plugin-transform-async-to-module-method",
"licensePeriod": "perpetual",
"material": "material",
"licenseType": "MIT",
"link": "https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-async-to-module-method",
"comment": "6.7.0"
},
{
"department": "kessler",
"relatedTo": "stuff",
"name": "babel-plugin-transform-class-properties",
"licensePeriod": "perpetual",
"material": "material",
"licenseType": "MIT",
"link": "https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-class-properties",
"comment": "6.5.0"
},
{
"department": "kessler",
"relatedTo": "stuff",
"name": "babel-plugin-transform-export-default-name",
"licensePeriod": "perpetual",
"material": "material",
"licenseType": "BSD-3-Clause",
"link": "git+https://github.com/gajus/babel-plugin-transform-export-default-name.git",
"comment": "1.0.4"
},
{
"department": "kessler",
"relatedTo": "stuff",
"name": "babel-plugin-transform-object-rest-spread",
"licensePeriod": "perpetual",
"material": "material",
"licenseType": "MIT",
"link": "https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-object-rest-spread",
"comment": "6.5.0"
},
{
"department": "kessler",
"relatedTo": "stuff",
"name": "babel-plugin-transform-runtime",
"licensePeriod": "perpetual",
"material": "material",
"licenseType": "MIT",
"link": "https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-runtime",
"comment": "6.5.2"
},
{
"department": "kessler",
"relatedTo": "stuff",
"name": "babel-preset-es2015",
"licensePeriod": "perpetual",
"material": "material",
"licenseType": "MIT",
"link": "https://github.com/babel/babel/tree/master/packages/babel-preset-es2015",
"comment": "6.5.0"
},
{
"department": "kessler",
"relatedTo": "stuff",
"name": "babel-preset-react",
"licensePeriod": "perpetual",
"material": "material",
"licenseType": "MIT",
"link": "https://github.com/babel/babel/tree/master/packages/babel-preset-react",
"comment": "6.5.0"
},
{
"department": "kessler",
"relatedTo": "stuff",
"name": "babel-register",
"licensePeriod": "perpetual",
"material": "material",
"licenseType": "MIT",
"link": "https://github.com/babel/babel/tree/master/packages/babel-register",
"comment": "6.5.2"
},
{
"department": "kessler",
"relatedTo": "stuff",
@ -29,6 +149,26 @@
"link": "https://github.com/jshttp/basic-auth",
"comment": "1.0.3"
},
{
"department": "kessler",
"relatedTo": "stuff",
"name": "basic-auth-connect",
"licensePeriod": "perpetual",
"material": "material",
"licenseType": "MIT",
"link": "https://github.com/expressjs/basic-auth-connect.git",
"comment": "1.0.0"
},
{
"department": "kessler",
"relatedTo": "stuff",
"name": "bluebird",
"licensePeriod": "perpetual",
"material": "material",
"licenseType": "MIT",
"link": "git://github.com/petkaantonov/bluebird.git",
"comment": "3.3.4"
},
{
"department": "kessler",
"relatedTo": "stuff",
@ -216,7 +356,7 @@
"material": "material",
"licenseType": "MIT",
"link": "git://github.com/Hacker0x01/react-datepicker.git",
"comment": "0.18.0"
"comment": "0.25.0"
},
{
"department": "kessler",
@ -236,7 +376,7 @@
"material": "material",
"licenseType": "ISC",
"link": "git+https://github.com/bluejamesbond/Influx.js.git",
"comment": "1.0.8"
"comment": "1.1.5"
},
{
"department": "kessler",

89
dist/libs/JSON2.js vendored
View file

@ -4,10 +4,18 @@ Object.defineProperty(exports, "__esModule", {
value: true
});
var _keys = require('babel-runtime/core-js/object/keys');
var _keys2 = _interopRequireDefault(_keys);
var _stringify = require('babel-runtime/core-js/json/stringify');
var _stringify2 = _interopRequireDefault(_stringify);
var _typeof2 = require('babel-runtime/helpers/typeof');
var _typeof3 = _interopRequireDefault(_typeof2);
var _promise = require('babel-runtime/core-js/promise');
var _promise2 = _interopRequireDefault(_promise);
@ -19,7 +27,7 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
function parse(str, reviver) {
return JSON.parse(str, function (k, v) {
var rv = undefined;
var rv = void 0;
if (typeof v === 'string') {
if (/^Promise \{(.*?)}$/g.test(v)) {
return new _promise2.default(function (r, e) {
@ -27,7 +35,7 @@ function parse(str, reviver) {
});
} else if (/^function ([a-zA-Z0-9_\$]*?)\((.*?)\)\s?\{(.*?)}$/g.test(rv = v.replace(/\n/g, ''))) {
try {
var x = undefined;
var x = void 0;
eval('x = ' + v);
if (x) {
return x;
@ -50,8 +58,77 @@ function parse(str, reviver) {
});
}
var stringifyOnce = function stringifyOnce(obj, replacer, indent) {
var printedObjects = [];
var printedObjectKeys = [];
function printOnceReplacer(key, value) {
if (printedObjects.length > 2000) {
// browsers will not print more than 20K, I don't see the point to allow 2K.. algorithm will not be fast anyway if we have too many objects
return 'object too long';
}
var printedObjIndex = false;
printedObjects.forEach(function (obj, index) {
if (obj === value) {
printedObjIndex = index;
}
});
if (key == '') {
//root element
printedObjects.push(obj);
printedObjectKeys.push("root");
return value;
} else if (printedObjIndex + "" != "false" && (typeof value === 'undefined' ? 'undefined' : (0, _typeof3.default)(value)) == "object") {
if (printedObjectKeys[printedObjIndex] == "root") {
return "(pointer to root)";
} else {
return "(see " + (!!value && !!value.constructor ? value.constructor.name.toLowerCase() : typeof value === 'undefined' ? 'undefined' : (0, _typeof3.default)(value)) + " with key " + printedObjectKeys[printedObjIndex] + ")";
}
} else {
var qualifiedKey = key || "(empty key)";
printedObjects.push(value);
printedObjectKeys.push(qualifiedKey);
if (replacer) {
return replacer(key, value);
} else {
return value;
}
}
}
return (0, _stringify2.default)(obj, printOnceReplacer, indent);
};
// http://www.z-car.com/blog/programming/how-to-escape-mongo-keys-using-node-js-in-a-flash
function escapeKeys(obj) {
if (!(Boolean(obj) && (typeof obj === 'undefined' ? 'undefined' : (0, _typeof3.default)(obj)) == 'object' && (0, _keys2.default)(obj).length > 0)) {
return false;
}
(0, _keys2.default)(obj).forEach(function (key) {
if ((0, _typeof3.default)(obj[key]) == 'object') {
escapeKeys(obj[key]);
} else {
if (key.indexOf('.') !== -1) {
var newkey = key.replace(/\./g, '_dot_');
obj[newkey] = obj[key];
delete obj[key];
}
if (key.indexOf('$') !== -1) {
var newkey = key.replace(/\$/g, '_amp_');
obj[newkey] = obj[key];
delete obj[key];
}
}
});
return true;
}
function stringify(obj, replacer, spaces) {
return (0, _stringify2.default)(obj, function (k, v) {
var objStr = stringifyOnce(obj, function (k, v) {
if (typeof v !== 'undefined' && v !== null) {
if (typeof v.then === 'function') {
return 'Promise { <pending> }';
@ -64,4 +141,10 @@ function stringify(obj, replacer, spaces) {
return v;
}, spaces);
var objCpy = JSON.parse(objStr);
escapeKeys(objCpy);
return (0, _stringify2.default)(objCpy);
}

View file

@ -18,14 +18,14 @@ var _createClass3 = _interopRequireDefault(_createClass2);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var _class = function () {
function _class() {
(0, _classCallCheck3.default)(this, _class);
var multiValueMap = function () {
function multiValueMap() {
(0, _classCallCheck3.default)(this, multiValueMap);
this._map = new _map2.default();
}
(0, _createClass3.default)(_class, [{
(0, _createClass3.default)(multiValueMap, [{
key: "get",
value: function get(key) {
if (!this._map.has(key)) {
@ -64,7 +64,7 @@ var _class = function () {
this._map.remove(old);
}
}]);
return _class;
return multiValueMap;
}();
exports.default = _class;
exports.default = multiValueMap;

14
dist/libs/time.js vendored Normal file
View file

@ -0,0 +1,14 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = time;
function time() {
if (typeof process !== 'undefined' && typeof process.hrtime === 'function') {
var duration = process.hrtime();
return duration[0] * 1000 + duration[1] / 1e6;
}
return Date.now();
}

85
dist/middleware/ExpressLogger.js vendored Normal file
View file

@ -0,0 +1,85 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _getIterator2 = require('babel-runtime/core-js/get-iterator');
var _getIterator3 = _interopRequireDefault(_getIterator2);
var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
var _createClass2 = require('babel-runtime/helpers/createClass');
var _createClass3 = _interopRequireDefault(_createClass2);
var _onFinished = require('on-finished');
var _onFinished2 = _interopRequireDefault(_onFinished);
var _time = require('../libs/time');
var _time2 = _interopRequireDefault(_time);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var ExpressLogger = function () {
function ExpressLogger(scribe) {
(0, _classCallCheck3.default)(this, ExpressLogger);
this.options = scribe.module('middleware/ExpressLogger').options;
this.scribe = scribe;
}
(0, _createClass3.default)(ExpressLogger, [{
key: 'getMiddleware',
value: function getMiddleware() {
var scribe = this.scribe;
var options = this.options;
return function (req, res, next) {
var start = (0, _time2.default)();
(0, _onFinished2.default)(res, function () {
var originalUrl = req.originalUrl;
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = (0, _getIterator3.default)(options.ignore), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var k = _step.value;
if (new RegExp(k).test(originalUrl)) {
return;
}
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
scribe[options.expose]({ req: req, res: res, duration: (0, _time2.default)() - start });
});
next();
};
}
}]);
return ExpressLogger;
}();
exports.default = ExpressLogger;

16
dist/middleware/index.js vendored Normal file
View file

@ -0,0 +1,16 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _ExpressLogger = require('./ExpressLogger');
Object.defineProperty(exports, 'ExpressLogger', {
enumerable: true,
get: function get() {
return _interopRequireDefault(_ExpressLogger).default;
}
});
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

View file

@ -8,10 +8,6 @@ var _assign = require('babel-runtime/core-js/object/assign');
var _assign2 = _interopRequireDefault(_assign);
var _getIterator2 = require('babel-runtime/core-js/get-iterator');
var _getIterator3 = _interopRequireDefault(_getIterator2);
var _map = require('babel-runtime/core-js/map');
var _map2 = _interopRequireDefault(_map);
@ -44,28 +40,19 @@ var _callsite = require('callsite');
var _callsite2 = _interopRequireDefault(_callsite);
var _onFinished = require('on-finished');
var _time2 = require('../libs/time');
var _onFinished2 = _interopRequireDefault(_onFinished);
var _time3 = _interopRequireDefault(_time2);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _time() {
if (typeof process !== 'undefined' && typeof process.hrtime === 'function') {
var duration = process.hrtime();
return duration[0] * 1000 + duration[1] / 1e6;
}
var basicConsole = function (_Console) {
(0, _inherits3.default)(basicConsole, _Console);
return Date.now();
}
function basicConsole(app, id) {
(0, _classCallCheck3.default)(this, basicConsole);
var _class = function (_Console) {
(0, _inherits3.default)(_class, _Console);
function _class(app, id) {
(0, _classCallCheck3.default)(this, _class);
var _this = (0, _possibleConstructorReturn3.default)(this, (0, _getPrototypeOf2.default)(_class).call(this, app, id));
var _this = (0, _possibleConstructorReturn3.default)(this, (0, _getPrototypeOf2.default)(basicConsole).call(this, app, id));
_this._times = new _map2.default();
@ -80,7 +67,7 @@ var _class = function (_Console) {
return _this;
}
(0, _createClass3.default)(_class, [{
(0, _createClass3.default)(basicConsole, [{
key: 'date',
value: function date() {
this.transient('date', new Date());
@ -88,56 +75,10 @@ var _class = function (_Console) {
}, {
key: 'time',
value: function time(label) {
this._times.set(label, _time());
this._times.set(label, (0, _time3.default)());
return this;
}
}, {
key: 'middleware',
value: function middleware() {
var _this2 = this;
var expose = arguments.length <= 0 || arguments[0] === undefined ? 'express' : arguments[0];
var ignore = arguments.length <= 1 || arguments[1] === undefined ? [/(scribe)/g] : arguments[1];
this.expose(expose);
ignore = Array.isArray(ignore) ? ignore : [ignore];
return function (req, res, next) {
var start = _time();
(0, _onFinished2.default)(res, function () {
var originalUrl = req.originalUrl;
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = (0, _getIterator3.default)(ignore), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var k = _step.value;
if (new RegExp(k).test(originalUrl)) return;
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
_this2[expose]({ req: req, res: res, duration: _time() - start });
});
next();
};
}
}, {
key: 'tag',
value: function tag() {
@ -153,8 +94,13 @@ var _class = function (_Console) {
key: 'metric',
value: function metric() {
var map = {};
for (var i = 1; i < arguments.length; i += 2) {
map[arguments.length <= i - 1 + 0 ? undefined : arguments[i - 1 + 0]] = arguments.length <= i + 0 ? undefined : arguments[i + 0];
for (var _len2 = arguments.length, metrics = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
metrics[_key2] = arguments[_key2];
}
for (var i = 1; i < metrics.length; i += 2) {
map[metrics[i - 1]] = metrics[i];
}
this.transient('metrics', (0, _assign2.default)(this.transient('metrics') || {}, map));
@ -164,7 +110,7 @@ var _class = function (_Console) {
}, {
key: 'trace',
value: function trace(text) {
this.transient('callsite', this.callSite(4));
this.transient('callsite', this.callSite(2));
return this.out('trace', new Error(text));
}
}, {
@ -175,16 +121,16 @@ var _class = function (_Console) {
throw new Error('No such label: ' + label);
}
this.captureStackTrace();
this._times.delete(label);
this.transient('callsite', this.callSite(5));
var duration = _time() - start;
var duration = (0, _time3.default)() - start;
return this.metric(label, duration).timing('');
}
}]);
return _class;
return basicConsole;
}(_Console3.default);
exports.default = _class;
exports.default = basicConsole;

View file

@ -12,6 +12,10 @@ var _promise = require('babel-runtime/core-js/promise');
var _promise2 = _interopRequireDefault(_promise);
var _typeof2 = require('babel-runtime/helpers/typeof');
var _typeof3 = _interopRequireDefault(_typeof2);
var _keys = require('babel-runtime/core-js/object/keys');
var _keys2 = _interopRequireDefault(_keys);
@ -34,22 +38,44 @@ var _async2 = _interopRequireDefault(_async);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var _class = function () {
function _class() {
var app = arguments.length <= 0 || arguments[0] === undefined ? 'Scribe' : arguments[0];
var id = arguments.length <= 1 || arguments[1] === undefined ? process.pid : arguments[1];
var console = function () {
function _class(config) {
(0, _classCallCheck3.default)(this, _class);
this._transient = {};
this._persistent = {};
this._pipelines = {};
this._exposed = {};
this._config = config;
this.persistent('app', app);
this.persistent('id', id);
this.persistent('app', this._config.app);
this.persistent('id', this._config.id);
}
(0, _createClass3.default)(_class, [{
key: 'module',
value: function module(namespace) {
var _this = this;
if (namespace) {
var _ret = function () {
var options = _this._config.module[namespace] || {};
options.isSet = function () {
return (0, _keys2.default)(options).length;
};
return {
v: { options: options }
};
}();
if ((typeof _ret === 'undefined' ? 'undefined' : (0, _typeof3.default)(_ret)) === "object") return _ret.v;
}
return this._config;
}
}, {
key: 'transient',
value: function transient(key, value) {
if (arguments.length === 1) {
@ -71,13 +97,26 @@ var _class = function () {
}
}, {
key: 'exposed',
value: function exposed() {
value: function exposed(expose) {
if (expose) {
return !!this._exposed[expose];
}
return (0, _keys2.default)(this._exposed);
}
}, {
key: 'captureStackTrace',
value: function captureStackTrace() {
var idx = arguments.length <= 0 || arguments[0] === undefined ? 3 : arguments[0];
if (!this.transient('callsite')) {
return this.transient('callsite', this.callSite(idx));
}
}
}, {
key: 'callSite',
value: function callSite() {
var idx = arguments.length <= 0 || arguments[0] === undefined ? 5 : arguments[0];
var idx = arguments.length <= 0 || arguments[0] === undefined ? 3 : arguments[0];
try {
var site = (0, _callsite2.default)()[idx];
@ -85,7 +124,7 @@ var _class = function () {
return {
line: site.getLineNumber(),
file: site.getFileName(),
func: site.getFunctionName()
func: site.getFunctionName() || 'anonymous'
};
return site;
@ -101,22 +140,24 @@ var _class = function () {
}, {
key: 'out',
value: function out(expose) {
var _this = this;
var _this2 = this;
for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
args[_key - 1] = arguments[_key];
}
this.captureStackTrace();
return new _promise2.default(function (resolve, reject) {
if (!_this._pipelines[expose]) {
if (!_this2._pipelines[expose]) {
return resolve();
}
_this.preout();
_this2.preout();
var pipelines = _this._pipelines[expose];
var transient = _this._transient;
var persistent = _this._persistent;
var pipelines = _this2._pipelines[expose];
var transient = _this2._transient;
var persistent = _this2._persistent;
var errs = [];
var now = new Date();
@ -125,7 +166,7 @@ var _class = function () {
}
if (!transient.callsite) {
transient.callsite = _this.callSite();
transient.callsite = _this2.callSite();
}
if (!args.length) {
@ -133,7 +174,7 @@ var _class = function () {
}
// reset before async
_this.reset();
_this2.reset();
_async2.default.forEachOfSeries(pipelines, function (pipeline, name, callback) {
var input = {
@ -223,4 +264,4 @@ var _class = function () {
return _class;
}();
exports.default = _class;
exports.default = console;

25
dist/reader/index.js vendored Normal file
View file

@ -0,0 +1,25 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _BasicConsole = require('./BasicConsole');
Object.defineProperty(exports, 'BasicConsole', {
enumerable: true,
get: function get() {
return _interopRequireDefault(_BasicConsole).default;
}
});
var _Console = require('./Console');
Object.defineProperty(exports, 'Console', {
enumerable: true,
get: function get() {
return _interopRequireDefault(_Console).default;
}
});
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

16
dist/router/index.js vendored Normal file
View file

@ -0,0 +1,16 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _Viewer = require('./Viewer');
Object.defineProperty(exports, 'Viewer', {
enumerable: true,
get: function get() {
return _interopRequireDefault(_Viewer).default;
}
});
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

252
dist/router/viewer.js vendored Normal file
View file

@ -0,0 +1,252 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _toConsumableArray2 = require('babel-runtime/helpers/toConsumableArray');
var _toConsumableArray3 = _interopRequireDefault(_toConsumableArray2);
var _maxSafeInteger = require('babel-runtime/core-js/number/max-safe-integer');
var _maxSafeInteger2 = _interopRequireDefault(_maxSafeInteger);
var _stringify = require('babel-runtime/core-js/json/stringify');
var _stringify2 = _interopRequireDefault(_stringify);
var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
var _createClass2 = require('babel-runtime/helpers/createClass');
var _createClass3 = _interopRequireDefault(_createClass2);
var _typeof2 = require('babel-runtime/helpers/typeof');
var _typeof3 = _interopRequireDefault(_typeof2);
var _express = require('express');
var _express2 = _interopRequireDefault(_express);
var _mongoose = require('mongoose');
var _mongoose2 = _interopRequireDefault(_mongoose);
var _jade = require('jade');
var _jade2 = _interopRequireDefault(_jade);
var _basicAuthConnect = require('basic-auth-connect');
var _basicAuthConnect2 = _interopRequireDefault(_basicAuthConnect);
var _bodyParser = require('body-parser');
var _bodyParser2 = _interopRequireDefault(_bodyParser);
var _JSONStream = require('JSONStream');
var _JSONStream2 = _interopRequireDefault(_JSONStream);
var _nwBuilder = require('nw-builder');
var _nwBuilder2 = _interopRequireDefault(_nwBuilder);
var _package = require('../../native/package.json');
var _package2 = _interopRequireDefault(_package);
var _url = require('url');
var _MongoDB = require('../writer/MongoDB');
var _extend = require('extend');
var _extend2 = _interopRequireDefault(_extend);
var _fs = require('fs');
var _fs2 = _interopRequireDefault(_fs);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function getObject(d, def) {
if (typeof d === 'undefined' || d === null) {
return def || {};
} else if ((typeof d === 'undefined' ? 'undefined' : (0, _typeof3.default)(d)) === 'object') {
return d;
} else {
try {
return JSON.parse(d);
} catch (e) {
return def || {};
}
}
}
var viewer = _jade2.default.compileFile(__dirname + '/../../views/viewer.jade');
var login = _jade2.default.compileFile(__dirname + '/../../views/login.jade');
var Viewer = function () {
function Viewer(scribe) {
(0, _classCallCheck3.default)(this, Viewer);
this.scribe = scribe;
}
(0, _createClass3.default)(Viewer, [{
key: 'getRouter',
value: function getRouter() {
var scribe = this.scribe;
var routerOptions = scribe.module('router/Viewer').options;
var clientOptions = scribe.module('router/Viewer/client').options;
var conn = void 0,
Entry = void 0;
_mongoose2.default.set('debug', routerOptions.debug);
conn = _mongoose2.default.createConnection(routerOptions.mongoUri);
Entry = conn.model('Entry', _MongoDB.EntrySchema);
var router = new _express.Router();
if (routerOptions.username && routerOptions.password) {
router.use((0, _basicAuthConnect2.default)(routerOptions.username, routerOptions.password));
}
router.use(_express2.default.static(__dirname + '/../../public'));
if (routerOptions.useBodyParser) {
router.use(_bodyParser2.default.json());
}
var render = function render(req, res) {
return res.send(viewer({ config: (0, _stringify2.default)(clientOptions) }));
};
router.get('/', render);
router.get('/viewer', render);
router.post('/rest/timeseries', function (req, res) {
try {
if (req.body && req.body.date) {
req.body.date = req.body.date || {};
if (req.body.date.$gt) req.body.date.$gt = new Date(req.body.date.$gt);
if (req.body.date.$gte) req.body.date.$gte = new Date(req.body.date.$gte);
if (req.body.date.$eq) req.body.date.$eq = new Date(req.body.date.$eq);
if (req.body.date.$lt) req.body.date.$lt = new Date(req.body.date.$lt);
if (req.body.date.$lte) req.body.date.$lte = new Date(req.body.date.$lte);
if (req.body._id && Array.isArray(req.body._id.$in)) req.body._id.$in = req.body._id.$in.map(function (a) {
return _mongoose2.default.Types.ObjectId(a);
});
if (req.body._id && typeof req.body._id === 'string') req.body._id = _mongoose2.default.Types.ObjectId(req.body._id);
}
} catch (e) {
// ignore
}
Entry.aggregate([{ $match: req.body }, {
$project: {
hour: {
year: { $year: '$date' },
month: { $month: '$date' },
day: { $dayOfMonth: '$date' },
hour: { $hour: '$date' }
},
expose: '$expose'
}
}, {
$group: {
_id: { hour: '$hour', expose: '$expose' },
number: { $sum: 1 }
}
}], function (err, out) {
return res.json(out || []);
});
});
router.get('/rest/db/:collection', function (req, res) {
var _Entry$find;
if (!routerOptions.mongoUri) {
return res.json({ err: 0, docs: [] });
}
var selector = getObject(req.query.selector);
var fields = typeof req.query.fields === 'string' ? req.query.fields : '';
var sort = getObject(req.query.sort, { _id: -1 });
var limit = !isNaN(req.query.limit) ? Math.max(0, parseInt(req.query.limit)) : _maxSafeInteger2.default;
var skip = !isNaN(req.query.skip) ? Math.max(0, parseInt(req.query.skip)) : 0;
res.type('application/json');
var stream = (_Entry$find = Entry.find(selector)).select.apply(_Entry$find, (0, _toConsumableArray3.default)(fields.split(' '))).skip(skip).limit(limit).sort(sort).stream();
stream.pipe(_JSONStream2.default.stringify()).pipe(res);
});
router.delete('/rest/db/:collection', function (req, res) {
if (!routerOptions.mongoUri) {
res.status(410);
return res.send();
}
var ids = req.query.id;
try {
ids = JSON.parse(ids);
} catch (e) {
// ignore
}
if (!Array.isArray(ids)) {
ids = [req.param('id')];
}
Entry.remove({ _id: { $in: ids } }, function (err) {
res.status(err ? 410 : 200);
res.send();
});
});
if (routerOptions.native) {}
return router;
}
}, {
key: 'getNative',
value: function getNative() {
var scribe = this.scribe;
var nativeOptions = scribe.module('router/Viewer/native').options;
// update
_package2.default.main = (0, _url.format)(nativeOptions);
// save
_fs2.default.writeFileSync(__dirname + '/../../native/package.json', (0, _stringify2.default)(_package2.default, null, 4), { encoding: 'utf8' });
var nw = new _nwBuilder2.default((0, _extend2.default)(true, {
platforms: ['win', 'osx', 'linux'],
buildDir: __dirname + '/../../public/native',
version: '0.12.3',
zip: true
}, nativeOptions, { files: __dirname + '/../../native/**/**' }));
if (nativeOptions.debug) {
nw.on('log', function (d) {
return console.log(d);
});
}
return nw.build();
}
}]);
return Viewer;
}();
exports.default = Viewer;

184
dist/routers/viewer.js vendored
View file

@ -1,184 +0,0 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _maxSafeInteger = require('babel-runtime/core-js/number/max-safe-integer');
var _maxSafeInteger2 = _interopRequireDefault(_maxSafeInteger);
var _stringify = require('babel-runtime/core-js/json/stringify');
var _stringify2 = _interopRequireDefault(_stringify);
var _assign = require('babel-runtime/core-js/object/assign');
var _assign2 = _interopRequireDefault(_assign);
var _typeof2 = require('babel-runtime/helpers/typeof');
var _typeof3 = _interopRequireDefault(_typeof2);
exports.create = create;
var _express = require('express');
var _express2 = _interopRequireDefault(_express);
var _mongoose = require('mongoose');
var _mongoose2 = _interopRequireDefault(_mongoose);
var _entry = require('../schemas/entry');
var _entry2 = _interopRequireDefault(_entry);
var _jade = require('jade');
var _jade2 = _interopRequireDefault(_jade);
var _basicAuth = require('basic-auth');
var _basicAuth2 = _interopRequireDefault(_basicAuth);
var _bodyParser = require('body-parser');
var _bodyParser2 = _interopRequireDefault(_bodyParser);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function getObject(d, def) {
if (typeof d === 'undefined' || d === null) {
return def || {};
} else if ((typeof d === 'undefined' ? 'undefined' : (0, _typeof3.default)(d)) === 'object') {
return d;
} else {
try {
return JSON.parse(d);
} catch (e) {
return def || {};
}
}
}
var viewer = _jade2.default.compileFile(__dirname + '/../../views/viewer.jade');
var login = _jade2.default.compileFile(__dirname + '/../../views/login.jade');
function create() {
var mongoUri = arguments.length <= 0 || arguments[0] === undefined ? 'mongodb://localhost/scribe' : arguments[0];
var routerConfig = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
var clientConfig = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2];
var debug = arguments.length <= 3 || arguments[3] === undefined ? false : arguments[3];
routerConfig = (0, _assign2.default)({
useBodyParser: true,
username: 'build',
password: 'build'
}, routerConfig);
var conn = undefined,
Entry = undefined;
if (mongoUri) {
_mongoose2.default.set('debug', debug);
conn = _mongoose2.default.createConnection(mongoUri);
Entry = conn.model('Entry', _entry2.default);
}
var router = new _express.Router();
var authenticate = function authenticate(req, res, next) {
function unauthorized(res) {
res.set('WWW-Authenticate', 'Basic realm=Authorization Required');
return res.sendStatus(401);
}
if (routerConfig.authentication === false || !routerConfig.username && !routerConfig.password) {
return next();
}
var user = (0, _basicAuth2.default)(req);
if (!user || !user.name || !user.pass) {
return unauthorized(res);
}
if (user.name === routerConfig.username && user.pass === routerConfig.password) {
return next();
} else {
return unauthorized(res);
}
};
router.use(authenticate);
router.use(_express2.default.static(__dirname + '/../../public'));
if (routerConfig.useBodyParser) {
router.use(_bodyParser2.default.json());
}
var renderViewer = function renderViewer(req, res) {
return res.send(viewer({ config: (0, _stringify2.default)(clientConfig) }));
};
router.get('/', renderViewer);
router.get('/viewer', renderViewer);
router.get('/rest/:collection', function (req, res) {
if (!mongoUri) {
return res.json({ err: 0, docs: [] });
}
var collection = req.params.collection;
var selector = getObject(req.query.selector);
var fields = typeof req.query.fields === 'string' ? req.query.fields : '';
var sort = getObject(req.query.sort, { _id: -1 });
var limit = !isNaN(req.query.limit) ? Math.max(0, parseInt(Number(req.query.limit))) : _maxSafeInteger2.default;
var col = Entry; // defaulting to Entry for now
if (!col) {
return res.json({ err: 1, docs: [] });
}
col.find(selector).select(fields).sort(sort).limit(limit).lean().exec(function () {
var err = arguments.length <= 0 || arguments[0] === undefined ? 0 : arguments[0];
var docs = arguments.length <= 1 || arguments[1] === undefined ? [] : arguments[1];
return res.json({ err: err, docs: docs });
});
});
router.delete('/rest/:collection', function (req, res) {
if (!mongoUri) {
res.status(410);
return res.send();
}
var collection = rreq.params.collection;
var ids = req.query.id;
try {
ids = JSON.parse(ids);
} catch (e) {
// ignore
}
if (!Array.isArray(ids)) {
ids = [req.param('id')];
}
var col = Entry; // defaulting to Entry for now
if (col) {
return col.remove({ _id: { $in: ids } }, function (err) {
res.status(err ? 410 : 200);
res.send();
});
}
res.status(410);
res.send();
});
return router;
}

20
dist/schemas/entry.js vendored
View file

@ -1,20 +0,0 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _mongoose = require('mongoose');
var EntrySchema = new _mongoose.Schema({
transient: _mongoose.Schema.Types.Object,
persistent: _mongoose.Schema.Types.Object,
args: _mongoose.Schema.Types.Object,
date: { type: Date, default: Date.now },
expose: String,
serialized: String // experimental
});
EntrySchema.index({ 'persistent.app': 1, 'persistent.id': -1 });
exports.default = EntrySchema;

277
dist/scribe.js vendored
View file

@ -3,90 +3,26 @@
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.Transform = exports.Reader = exports.Writer = undefined;
exports.Writer = exports.Transform = exports.Router = exports.Reader = exports.Middleware = undefined;
var _stringify = require('babel-runtime/core-js/json/stringify');
var _keys = require('babel-runtime/core-js/object/keys');
var _stringify2 = _interopRequireDefault(_stringify);
var _keys2 = _interopRequireDefault(_keys);
exports.default = function () {
var id = arguments.length <= 0 || arguments[0] === undefined ? process.pid : arguments[0];
var opts = arguments.length <= 1 || arguments[1] === undefined ? (0, _rc2.default)('scribe', defaultOpts) : arguments[1];
var _toConsumableArray2 = require('babel-runtime/helpers/toConsumableArray');
opts = (0, _extend2.default)(true, {}, defaultOpts, opts);
var _toConsumableArray3 = _interopRequireDefault(_toConsumableArray2);
var console = new _BasicConsole2.default(opts.name, id || opts.instanceId);
var _typeof2 = require('babel-runtime/helpers/typeof');
function appendTransforms(args) {
if (opts.mongo && opts.mongoUri && opts.socket && opts.socketPort) {
args.push(new _JSON2Converter2.default());
args.push(new _MongoDB2.default(opts.mongoUri, opts.debug));
args.push(new _SocketIO2.default(opts.socketPort, opts.debug));
} else if (opts.mongo && opts.mongoUri) {
args.push(new _JSON2Converter2.default());
args.push(new _MongoDB2.default(opts.mongoUri, opts.debug));
} else if (opts.socket && opts.socketPort) {
args.push(new _JSON2Converter2.default());
args.push(new _SocketIO2.default(opts.socketPort, opts.debug));
}
var _typeof3 = _interopRequireDefault(_typeof2);
return args;
}
var _getIterator2 = require('babel-runtime/core-js/get-iterator');
for (var _len = arguments.length, exposers = Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {
exposers[_key - 2] = arguments[_key];
}
var _getIterator3 = _interopRequireDefault(_getIterator2);
console.exposed().concat(exposers).forEach(function (expose) {
console.expose(expose);
var args = appendTransforms([expose, 'mongo-socket', new _ErrorExtractor2.default()]);
console.pipe.apply(console, args);
console.pipe(expose, 'bash', new _Inspector2.default(opts.inspector), new _DefaultConsole2.default());
});
var args = appendTransforms(['express', 'mongo-socket', new _ErrorExtractor2.default(), new _ExpressExtractor2.default()]);
console.pipe.apply(console, args);
console.pipe('express', 'bash', new _ExpressExtractor2.default(), new _ExpressInspector2.default(), new _Inspector2.default(opts.inspector), new _DefaultConsole2.default());
console.viewer = _viewer.create.bind(null, opts.mongo && opts.mongoUri, opts.web.router, opts.web.client, opts.debug);
console.build = function () {
// update
_package2.default.main = opts.publicUri + ':' + _path2.default.join(String(opts.web.client.port), opts.basePath);
// save
_fs2.default.writeFileSync(__dirname + '/../native/package.json', (0, _stringify2.default)(_package2.default, null, 4), { encoding: 'utf8' });
var nw = new _nwBuilder2.default((0, _extend2.default)(true, {
platforms: ['win', 'osx', 'linux'],
buildDir: __dirname + '/../public/native',
version: '0.12.3',
zip: true
}, opts.nwjs, { files: __dirname + '/../native/**/**' }));
if (opts.debug) {
nw.on('log', function (d) {
return console.log(d);
});
}
return nw.build();
};
process.on('uncaughtException', function (e) {
return console.error(e).then(function () {
return process.exit(1);
});
});
return console;
};
exports.resolvePipeline = resolvePipeline;
exports.create = create;
var _fs = require('fs');
@ -96,111 +32,114 @@ var _path = require('path');
var _path2 = _interopRequireDefault(_path);
var _Console = require('./readers/Console');
var _Console2 = _interopRequireDefault(_Console);
var _BasicConsole = require('./readers/BasicConsole');
var _BasicConsole2 = _interopRequireDefault(_BasicConsole);
var _Inspector = require('./transforms/Inspector');
var _Inspector2 = _interopRequireDefault(_Inspector);
var _ExpressInspector = require('./transforms/ExpressInspector');
var _ExpressInspector2 = _interopRequireDefault(_ExpressInspector);
var _ExpressExtractor = require('./transforms/ExpressExtractor');
var _ExpressExtractor2 = _interopRequireDefault(_ExpressExtractor);
var _JSON2Converter = require('./transforms/JSON2Converter');
var _JSON2Converter2 = _interopRequireDefault(_JSON2Converter);
var _ErrorExtractor = require('./transforms/ErrorExtractor');
var _ErrorExtractor2 = _interopRequireDefault(_ErrorExtractor);
var _MongoDB = require('./writers/MongoDB');
var _MongoDB2 = _interopRequireDefault(_MongoDB);
var _SocketIO = require('./writers/SocketIO');
var _SocketIO2 = _interopRequireDefault(_SocketIO);
var _DefaultConsole = require('./writers/DefaultConsole');
var _DefaultConsole2 = _interopRequireDefault(_DefaultConsole);
var _viewer = require('./routers/viewer');
var _nwBuilder = require('nw-builder');
var _nwBuilder2 = _interopRequireDefault(_nwBuilder);
var _rc = require('rc');
var _rc2 = _interopRequireDefault(_rc);
var _package = require('./../native/package.json');
var _package2 = _interopRequireDefault(_package);
var _extend = require('extend');
var _extend2 = _interopRequireDefault(_extend);
var _middleware = require('./middleware');
var Middleware = _interopRequireWildcard(_middleware);
var _reader = require('./reader');
var Reader = _interopRequireWildcard(_reader);
var _router = require('./router');
var Router = _interopRequireWildcard(_router);
var _transform = require('./transform');
var Transform = _interopRequireWildcard(_transform);
var _writer = require('./writer');
var Writer = _interopRequireWildcard(_writer);
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var Writer = exports.Writer = { MongoDB: _MongoDB2.default, DefaultConsole: _DefaultConsole2.default };
var Reader = exports.Reader = { BasicConsole: _BasicConsole2.default, Console: _Console2.default };
var Transform = exports.Transform = { Inspector: _Inspector2.default, ExpressInspector: _ExpressInspector2.default, ExpressExtractor: _ExpressExtractor2.default, ErrorExtractor: _ErrorExtractor2.default, JSON2Converter: _JSON2Converter2.default };
exports.Middleware = Middleware;
exports.Reader = Reader;
exports.Router = Router;
exports.Transform = Transform;
exports.Writer = Writer;
function resolvePipeline(scribe, pipeline) {
var resolved = [];
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
var defaultOpts = {
name: 'Scribe',
mongoUri: 'mongodb://localhost/scribe',
mongo: true,
basePath: 'scribe/',
socketPort: 4000,
socket: true,
inspector: {
colors: true,
showHidden: false,
depth: 5,
pre: true,
callsite: true,
tags: true,
args: true,
metrics: true
},
web: {
router: {
username: 'build',
password: 'build',
authentication: true,
sessionSecret: 'scribe-session',
useBodyParser: true,
useSession: true
},
client: {
socketPorts: [4000],
exposed: {
all: { label: 'all', query: { expose: { $exists: true } } }
try {
for (var _iterator = (0, _getIterator3.default)(pipeline), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var through = _step.value;
if (typeof through === 'function') {
resolved.push(new through(scribe));
} else if ((typeof through === 'undefined' ? 'undefined' : (0, _typeof3.default)(through)) === 'object') {
resolved.push(through);
} else if (typeof through === 'string') {
var Class = require(_path2.default.join(__dirname, through)).default;
resolved.push(new Class(scribe));
}
}
},
//error: {label: 'error', query: {expose: 'error'}},
//express: {label: 'express', query: {expose: 'express'}},
//info: {label: 'info', query: {expose: 'info'}},
//log: {label: 'log', query: {expose: 'log'}},
//warn: {label: 'warn', query: {expose: 'warn'}},
//trace: {label: 'trace', query: {expose: 'trace'}}
nwjs: {},
debug: false
};
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
;
return resolved;
}
function create(opts) {
opts = (0, _extend2.default)(true, (0, _rc2.default)('scribe', {}), opts);
// create default console
var console = new Reader.BasicConsole(opts);
var _opts = opts;
var exposeMap = _opts.expose;
var pipelineMap = _opts['expose/pipeline'];
[].concat((0, _toConsumableArray3.default)(console.exposed()), (0, _toConsumableArray3.default)((0, _keys2.default)(exposeMap))).forEach(function (expose) {
if (expose === 'default') return;
var pipelines = exposeMap[expose] || exposeMap.default;
if (Array.isArray(pipelines)) {
pipelines.forEach(function (pipeline) {
if (Array.isArray(pipelineMap[pipeline])) {
if (opts.debug) {
process.stdout.write('Exposing ' + expose + ' through ' + pipeline + '\n');
}
console.expose(expose);
console.pipe.apply(console, [expose, pipeline].concat((0, _toConsumableArray3.default)(resolvePipeline(console, pipelineMap[pipeline]))));
}
});
}
});
if (opts.handleUncaughtException) {
process.on('uncaughtException', function (e) {
return console.error(e).then(function () {
return process.exit(1);
});
});
}
return console;
}

View file

@ -27,7 +27,7 @@ var ErrorExtractor = function () {
key: "through",
value: function through(data, callback) {
data.args = data.args.map(function (a) {
return a instanceof Error ? JSON.parse((0, _stringify2.default)(a, ["message", "arguments", "type", "name", "stack"])) : a;
return a instanceof Error ? a.toJSON ? a.toJSON() : JSON.parse((0, _stringify2.default)(a, ["message", "arguments", "type", "name", "stack", "code"])) : a;
});
callback(null, data);

View file

@ -30,7 +30,7 @@ var ExpressExtractor = function () {
(0, _createClass3.default)(ExpressExtractor, [{
key: 'ip',
value: function ip(req) {
return req.ip || req._remoteAddress || req.remoteAddress || req.connection && req.connection.remoteAddress || req.headers['x-forwarded-for'] || undefined;
return req.ip || req._remoteAddress || req.remoteAddress || req.connection && req.connection.remoteAddress || req.headers['x-forwarded-for'] || 'xx.x.x.xx';
}
}, {
key: 'header',

View file

@ -48,7 +48,7 @@ var ExpressInspector = function () {
symbol = '';
}
var ret = undefined;
var ret = void 0;
if (this.symbols) {
ret = symbol + ' ' + stat;
} else {
@ -63,7 +63,7 @@ var ExpressInspector = function () {
var express = data.args[0];
data.transient['callsite'] = '' + express.ip;
data.args = [_chalk2.default.bgGreen.black(' ' + express.method + ' ') + ' ' + _chalk2.default.gray(express.url) + ' ' + this.status(express) + ' - ' + express.contentLength + ' ' + _chalk2.default.gray('(' + express.duration.toFixed(3) + 'ms)')];
data.args = [_chalk2.default.bgGreen.black(' ' + express.method + ' ') + ' ' + _chalk2.default.gray(express.originalUrl) + ' ' + this.status(express) + ' - ' + express.contentLength + ' ' + _chalk2.default.gray('(' + express.duration.toFixed(3) + 'ms)')];
callback(null, data);
}

38
dist/transform/FullTextSerialize.js vendored Normal file
View file

@ -0,0 +1,38 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
var _createClass2 = require('babel-runtime/helpers/createClass');
var _createClass3 = _interopRequireDefault(_createClass2);
var _JSON = require('../libs/JSON2');
var JSON2 = _interopRequireWildcard(_JSON);
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var FullTextSerialize = function () {
function FullTextSerialize() {
(0, _classCallCheck3.default)(this, FullTextSerialize);
}
(0, _createClass3.default)(FullTextSerialize, [{
key: 'through',
value: function through(data, callback) {
data.serialized = JSON2.stringify(data);
callback(null, data);
}
}]);
return FullTextSerialize;
}();
exports.default = FullTextSerialize;

View file

@ -39,12 +39,10 @@ function inspect(x, ctx) {
}
var Inspector = function () {
function Inspector() {
var inspectOpts = arguments.length <= 0 || arguments[0] === undefined ? { colors: true, showHidden: false,
depth: 5, pre: true, args: true, metrics: true, tags: true } : arguments[0];
function Inspector(scribe) {
(0, _classCallCheck3.default)(this, Inspector);
this.inspectOpts = inspectOpts;
this.options = scribe.module('transform/Inspector').options;
}
(0, _createClass3.default)(Inspector, [{
@ -54,7 +52,7 @@ var Inspector = function () {
args[_key - 1] = arguments[_key];
}
var ctx = this.inspectOpts;
var ctx = this.options;
if (typeof f !== 'string') {
var objects = [];
@ -156,11 +154,11 @@ var Inspector = function () {
data.args = '';
}
var pre = this.inspectOpts.pre ? this.inspectPre(data) + ' ' : '';
var tags = this.inspectOpts.tags ? this.inspectTags(data) : '';
var metrics = this.inspectOpts.metrics ? this.inspectMetrics(data) : '';
var site = this.inspectOpts.callsite ? this.inspectCallSite(data) : '';
var pretty = this.inspectOpts.args ? this.inspectArguments(data) : '';
var pre = this.options.pre ? this.inspectPre(data) + ' ' : '';
var tags = this.options.tags ? this.inspectTags(data) : '';
var metrics = this.options.metrics ? this.inspectMetrics(data) : '';
var site = this.options.callsite ? this.inspectCallSite(data) : '';
var pretty = this.options.args ? this.inspectArguments(data) : '';
var inspected = pretty.split('\n').map(function (line) {
return pre + ' ' + [tags, metrics, line, site].join(' ');
});

View file

@ -20,18 +20,18 @@ function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj;
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var JSON2Converter = function () {
function JSON2Converter() {
(0, _classCallCheck3.default)(this, JSON2Converter);
var ToJSON2 = function () {
function ToJSON2() {
(0, _classCallCheck3.default)(this, ToJSON2);
}
(0, _createClass3.default)(JSON2Converter, [{
(0, _createClass3.default)(ToJSON2, [{
key: 'through',
value: function through(data, callback) {
callback(null, JSON.parse(JSON2.stringify(data)));
}
}]);
return JSON2Converter;
return ToJSON2;
}();
exports.default = JSON2Converter;
exports.default = ToJSON2;

52
dist/transform/index.js vendored Normal file
View file

@ -0,0 +1,52 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _ErrorExtractor = require('./ErrorExtractor');
Object.defineProperty(exports, 'ErrorExtractor', {
enumerable: true,
get: function get() {
return _interopRequireDefault(_ErrorExtractor).default;
}
});
var _ExpressExtractor = require('./ExpressExtractor');
Object.defineProperty(exports, 'ExpressExtractor', {
enumerable: true,
get: function get() {
return _interopRequireDefault(_ExpressExtractor).default;
}
});
var _ExpressInspector = require('./ExpressInspector');
Object.defineProperty(exports, 'ExpressInspector', {
enumerable: true,
get: function get() {
return _interopRequireDefault(_ExpressInspector).default;
}
});
var _Inspector = require('./Inspector');
Object.defineProperty(exports, 'Inspector', {
enumerable: true,
get: function get() {
return _interopRequireDefault(_Inspector).default;
}
});
var _ToJSON = require('./ToJSON2');
Object.defineProperty(exports, 'ToJSON2', {
enumerable: true,
get: function get() {
return _interopRequireDefault(_ToJSON).default;
}
});
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

View file

@ -3,6 +3,7 @@
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.EntrySchema = undefined;
var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
@ -16,29 +17,55 @@ var _mongoose = require('mongoose');
var _mongoose2 = _interopRequireDefault(_mongoose);
var _entry = require('../schemas/entry');
var _entry2 = _interopRequireDefault(_entry);
var _JSON = require('../libs/JSON2');
var JSON2 = _interopRequireWildcard(_JSON);
var _express = require('express');
var _express2 = _interopRequireDefault(_express);
var _jade = require('jade');
var _jade2 = _interopRequireDefault(_jade);
var _basicAuthConnect = require('basic-auth-connect');
var _basicAuthConnect2 = _interopRequireDefault(_basicAuthConnect);
var _bodyParser = require('body-parser');
var _bodyParser2 = _interopRequireDefault(_bodyParser);
var _JSONStream = require('JSONStream');
var _JSONStream2 = _interopRequireDefault(_JSONStream);
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var EntrySchema = exports.EntrySchema = new _mongoose.Schema({
transient: _mongoose.Schema.Types.Object,
persistent: _mongoose.Schema.Types.Object,
args: _mongoose.Schema.Types.Object,
date: { type: Date, default: Date.now },
expose: String,
serialized: String
});
EntrySchema.index({ 'persistent.app': 1, 'persistent.id': -1 });
EntrySchema.index({ 'serialized': 1, 'comment': 'text' });
var MongoDB = function () {
function MongoDB() {
var address = arguments.length <= 0 || arguments[0] === undefined ? 'mongodb://localhost/scribe' : arguments[0];
var debug = arguments.length <= 1 || arguments[1] === undefined ? false : arguments[1];
function MongoDB(scribe) {
(0, _classCallCheck3.default)(this, MongoDB);
_mongoose2.default.set('debug', debug);
this.options = scribe.module('writer/MongoDB').options;
var conn = _mongoose2.default.createConnection(address);
this._debug = debug;
this._entry = conn.model('Entry', _entry2.default);
var conn = _mongoose2.default.createConnection(this.options.uri);
this._debug = this.options.debug;
this._entry = conn.model('Entry', EntrySchema);
}
(0, _createClass3.default)(MongoDB, [{

View file

@ -18,18 +18,16 @@ var _socket2 = _interopRequireDefault(_socket);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var sio = undefined;
var sio = void 0;
var SocketIO = function () {
function SocketIO() {
var port = arguments.length <= 0 || arguments[0] === undefined ? 4000 : arguments[0];
var debug = arguments.length <= 1 || arguments[1] === undefined ? false : arguments[1];
function SocketIO(scribe) {
(0, _classCallCheck3.default)(this, SocketIO);
process.env.DEBUG = debug && 'socket.io*';
this.options = scribe.module('writer/SocketIO').options;
if (!sio) {
sio = (0, _socket2.default)(port);
sio = (0, _socket2.default)(this.options.port, this.options.options);
}
}

34
dist/writer/index.js vendored Normal file
View file

@ -0,0 +1,34 @@
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _DefaultConsole = require('./DefaultConsole');
Object.defineProperty(exports, 'DefaultConsole', {
enumerable: true,
get: function get() {
return _interopRequireDefault(_DefaultConsole).default;
}
});
var _MongoDB = require('./MongoDB');
Object.defineProperty(exports, 'MongoDB', {
enumerable: true,
get: function get() {
return _interopRequireDefault(_MongoDB).default;
}
});
var _SocketIO = require('./SocketIO');
Object.defineProperty(exports, 'SocketIO', {
enumerable: true,
get: function get() {
return _interopRequireDefault(_SocketIO).default;
}
});
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

View file

@ -1,7 +1,7 @@
import cluster from 'cluster'
import http from 'http'
import express from 'express'
import Scribe from '../index.js'
import * as Scribe from '../index.js';
import * as JSON2 from '../src/libs/JSON2'
const port = 4005;
@ -13,40 +13,23 @@ if (cluster.isMaster) {
cluster.fork();
}
} else {
const console = new Scribe(cluster.worker.id, {
name: 'ScribeCluster',
mongoUri: 'mongodb://localhost/scribe',
publicUri: 'http://localhost',
basePath: 'scribe/',
socketPort: socketPort + cluster.worker.id - 1, // assign a port to a worker worker
web: {
router: {
username: 'build',
password: 'build',
// authentication must be implemented by yourself
// if you are in cluster mode
authentication: false,
sessionSecret: 'scribe-session',
useBodyParser: true,
useSession: true
const options = {
"app": 'cluster-server',
"id": process.pid,
"module": {
"writer/SocketIO": {
"port": socketPort + cluster.worker.id - 1,
"options": {}
},
client: {
port: port,
socketPorts: [socketPort, socketPort + 1, socketPort + 2, socketPort + 3],
exposed: {
all: {label: 'all', query: {expose: {$exists: true}}},
error: {label: 'error', query: {expose: 'error'}},
express: {label: 'express', query: {expose: 'express'}},
info: {label: 'info', query: {expose: 'info'}},
log: {label: 'log', query: {expose: 'log'}},
warn: {label: 'warn', query: {expose: 'warn'}},
trace: {label: 'trace', query: {expose: 'trace'}},
perf: {label: 'perf', query: {expose: 'perf'}}
}
"router/Viewer/client": {
"background": "#131B21",
"socketPorts": [socketPort, socketPort + 1, socketPort + 2, socketPort + 3]
}
},
debug: false
});
"debug": false
};
const console = Scribe.create(options);
// default tags
console.persistent('tags', ['mocha', 'scribe']);
@ -61,9 +44,14 @@ if (cluster.isMaster) {
// express
const app = express();
const logger = new Scribe.Middleware.ExpressLogger(console);
const viewer = new Scribe.Router.Viewer(console);
// express logger
app.use(console.middleware('express'));
app.use(logger.getMiddleware());
// viewer
app.use('/scribe', viewer.getRouter());
// test harness
app.get('/test', (req, res) => {
@ -72,16 +60,8 @@ if (cluster.isMaster) {
});
});
// viewer
app.use('/scribe', console.viewer());
app.listen(port, () => console.log(`Listening to ${port} - Cluster ${cluster.worker.id}`));
// build native app
if (cluster.worker.id == 1) {
console.build().then(()=> console.log('Created native apps!')).catch(err => console.error(err));
}
// override default console
console.override();

View file

@ -1,73 +1,70 @@
import express from 'express'
import Scribe from '../index.js'
import * as Scribe from '../index.js';
import * as JSON2 from '../src/libs/JSON2'
const port = 4005;
const socketPort = 3000;
const console = new Scribe(process.pid, {
name: 'Scribe',
mongoUri: 'mongodb://localhost/scribe',
mongo: false,
publicUri: 'http://localhost',
basePath: 'scribe/',
socketPort: socketPort,
web: {
router: {
username: 'build',
password: 'build',
authentication: true,
sessionSecret: 'scribe-session',
useBodyParser: true,
useSession: true
const options = {
"app": 'simple-server-stream-only',
"id": process.pid,
"expose": {
"default": [
"socket",
"bash"
],
"express": [
"express-socket",
"express-bash"
]
},
"expose/pipeline": {
"socket": [
"transform/ErrorExtractor",
"transform/ToJSON2",
"transform/FullTextSerialize",
"writer/SocketIO"
],
"express-socket": [
"transform/ExpressExtractor",
"transform/ErrorExtractor",
"transform/ToJSON2",
"transform/FullTextSerialize",
"writer/SocketIO"
]
},
"module": {
"writer/SocketIO": {
"port": socketPort,
"options": {}
},
client: {
port: port,
socketPorts: [socketPort],
exposed: {
all: {label: 'all', query: {expose: {$exists: true}}},
error: {label: 'error', query: {expose: 'error'}},
express: {label: 'express', query: {expose: 'express'}},
info: {label: 'info', query: {expose: 'info'}},
log: {label: 'log', query: {expose: 'log'}},
warn: {label: 'warn', query: {expose: 'warn'}},
trace: {label: 'trace', query: {expose: 'trace'}},
timing: {label: 'time', query: {expose: 'timing'}},
user: {label: 'user', query: {'transient.tags': {$in: ['USER ID']}}}
}
"router/Viewer/client": {
"background": "#131B21",
"socketPorts": [
socketPort
]
}
},
native: {},
debug: false
});
"debug": false
};
const console = Scribe.create(options);
console.time('serverStartup');
// default tags
console.persistent('tags', ['mocha', 'scribe']);
// modify an existing pipeline i.e. the express
console.pipe('express', 'mongo-socket').unshift({
through(data, callback){
const {req, res} = data.args[0]; // access the req, res objects
// modify data as needed
// i.e.
// add user tags
const tags = data.transient.tags || [];
tags.push('USER_ID'); // perhaps put in the user id here
data.transient.tags = tags;
callback(null, data);
}
});
// express
const app = express();
const logger = new Scribe.Middleware.ExpressLogger(console);
const viewer = new Scribe.Router.Viewer(console);
// express logger
app.use(console.middleware('express'));
app.use(logger.getMiddleware());
// viewer
app.use('/scribe', viewer.getRouter());
// test harness
app.get('/test', (req, res) => {
@ -76,9 +73,6 @@ app.get('/test', (req, res) => {
});
});
// viewer
app.use('/scribe', console.viewer());
app.listen(port, () => {
console.log(`Listening to ${port}`);
@ -97,6 +91,3 @@ app.listen(port, () => {
console.timeEnd('serverStartup');
});
// build native app
console.build().then(()=> console.log('Created native apps!')).catch(err => console.error(err));

View file

@ -1,52 +1,34 @@
import express from 'express'
import Scribe from '../index.js'
import * as JSON2 from '../src/libs/JSON2'
import express from 'express';
import * as Scribe from '../index.js';
import * as JSON2 from '../src/libs/JSON2';
const port = 4005;
const socketPort = 50000;
const console = new Scribe(process.pid, {
name: 'Scribe',
mongoUri: 'mongodb://localhost/scribe',
publicUri: 'http://localhost',
basePath: 'scribe/',
socketPort: socketPort,
web: {
router: {
username: 'build',
password: 'build',
authentication: true,
sessionSecret: 'scribe-session',
useBodyParser: true,
useSession: true
const options = {
"app": 'simple-server',
"id": process.pid,
"module": {
"writer/SocketIO": {
"port": 50000,
"options": {}
},
client: {
port: port,
socketPorts: [socketPort],
exposed: {
all: {label: 'all', query: {expose: {$exists: true}}},
error: {label: 'error', query: {expose: 'error'}},
express: {label: 'express', query: {expose: 'express'}},
info: {label: 'info', query: {expose: 'info'}},
log: {label: 'log', query: {expose: 'log'}},
warn: {label: 'warn', query: {expose: 'warn'}},
trace: {label: 'trace', query: {expose: 'trace'}},
timing: {label: 'time', query: {expose: 'timing'}},
user: {label: 'user', query: {'transient.tags': {$in: ['USER ID']}}}
}
"router/Viewer/client": {
"background": "#131B21",
"socketPorts": [
50000
]
}
},
native: {},
debug: false
});
"debug": false
};
const console = Scribe.create(options);
console.time('serverStartup');
// default tags
console.persistent('tags', ['mocha', 'scribe']);
// modify an existing pipeline i.e. the express
console.pipe('express', 'mongo-socket').unshift({
var through = {
through(data, callback){
const {req, res} = data.args[0]; // access the req, res objects
@ -60,13 +42,22 @@ console.pipe('express', 'mongo-socket').unshift({
callback(null, data);
}
});
};
// modify an existing pipeline i.e. the express
console.pipe('express', 'express-mongo-socket')
.unshift(through);
// express
const app = express();
const logger = new Scribe.Middleware.ExpressLogger(console);
const viewer = new Scribe.Router.Viewer(console);
// express logger
app.use(console.middleware('express'));
app.use(logger.getMiddleware());
// viewer
app.use('/scribe', viewer.getRouter());
// test harness
app.get('/test', (req, res) => {
@ -75,9 +66,6 @@ app.get('/test', (req, res) => {
});
});
// viewer
app.use('/scribe', console.viewer());
app.listen(port, () => {
console.log(`Listening to ${port}`);
@ -103,4 +91,6 @@ app.listen(port, () => {
});
// build native app
console.build().then(()=> console.log('Created native apps!')).catch(err => console.error(err));
viewer.getNative().then(()=> console.log('Created native apps!')).catch(err => console.error(err));
setInterval(() => console.log(`Heartbeat - ${Date.now()} ... every 5 seconds`), 5000);

View file

@ -1,88 +1,84 @@
/* jshint -W079 */
(function() {
var scribe = require('../src/scribe')(),
console = process.console,
express = require('express'),
path = require('path'),
app = express();
(function () {
var Scribe = require('../');
var express = require('express');
var path = require('path');
var bodyParser = require('body-parser');
var app = express();
console.addLogger('log', 'green');
console.addLogger('err', 'red');
const options = {
"app": 'simple-server',
"id": process.pid,
"module": {
"writer/MongoDB": {
"uri": process.env.MONGOLAB_URI || "mongodb://localhost/scribe"
},
"router/Viewer": {
"mongoUri": process.env.MONGOLAB_URI || "mongodb://localhost/scribe"
},
"writer/SocketIO": {
"port": 50000,
"options": {}
},
"router/Viewer/client": {
"background": "#222",
"socketPorts": [
50000
]
}
},
"debug": false
};
// port
app.set('port', (process.env.PORT || 5000));
const console = Scribe.create(options);
const logger = new Scribe.Middleware.ExpressLogger(console);
const viewer = new Scribe.Router.Viewer(console);
// public dir
app.use('/', express.static(path.join(__dirname, 'public')));
console.persistent('tags', ['scribe']);
// scribe
app.use(scribe.express.logger());
app.use('/logs', scribe.webPanel());
app.set('port', (process.env.PORT || 5000));
// index
app.get('/', function(req, res) {
res.sendFile(path.join(__dirname, 'views', 'index.html'));
});
app.use(logger.getMiddleware());
app.use(bodyParser.urlencoded({extended: true}));
app.use(bodyParser.json());
function _param(name) {
name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
var regex = new RegExp("[\\?&]" + name + "=([^&#]*)");
var results = regex.exec("https://r.com?" + this.body);
return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
app.use('/', express.static(path.join(__dirname, 'public')));
app.use('/scribe', viewer.getRouter());
app.post('/', function (req, res) {
var tag = req.param('tag');
var msg = req.param('msg');
if (!msg) {
return res.status(400).send('Param `msg` not defined');
}
// log
app.post('/', function(req, res) {
try {
msg = JSON.parse(msg);
} catch (e) {
// ignore
}
req.body = '';
req.setEncoding('utf8');
// print
if (tag) {
console.tag(tag).log(msg);
} else {
console.log(msg);
}
req.on('data', function(chunk) {
req.body += chunk;
});
res.status(200).send("Success! Check system logs to see your message.");
});
req.on('end', function() {
req.param = _param;
var port = app.get('port');
var tag = req.param('tag');
var msg = req.param('msg');
//log something every 10 minutes
setInterval(function () {
console.tag("Test").log("Hi there ! Server date : " + new Date());
}, 10 * 60 * 1000);
if (!msg) {
return res.status(400).send('Param `msg` not defined');
}
try {
msg = JSON.parse(msg);
} catch (e) {
// ignore
}
// print
if (tag) {
console.tag(tag).log(msg);
} else {
console.log(msg);
}
res.status(200).send("Success! Check system logs to see your message.");
});
req.on('close', function(err){
console.err(err);
res.status(400).json("Error! Check system logs to identify the error.");
});
});
var port = app.get('port');
//log something every 10 minutes
setInterval(function () {
console.tag("Test").log("Hi there ! Server date : " + new Date());
}, 10 * 60 * 1000);
app.listen(port, function() {
console.time().log('Server listening at port ' + port);
});
app.listen(port, function () {
console.time().log('Server listening at port ' + port);
});
})();

Binary file not shown.

Before

Width:  |  Height:  |  Size: 170 KiB

After

Width:  |  Height:  |  Size: 228 KiB

122
herokuapp/public/index.html Normal file
View file

@ -0,0 +1,122 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<base target="_frame"/>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>ScribeJS Demo</title>
<link href='https://fonts.googleapis.com/css?family=Open+Sans:400,300,600,700' rel='stylesheet' type='text/css'>
<link href='https://fonts.googleapis.com/css?family=Ubuntu+Mono:400,700' rel='stylesheet' type='text/css'>
<link href="css/style.css" rel="stylesheet" type="text/css">
<script src="https://code.jquery.com/jquery-2.2.3.min.js"
integrity="sha256-a23g1Nt4dtEYOj7bR+vTu7+T8VP13humZFBJNIYoEJo=" crossorigin="anonymous"></script>
<script src="js/index.js" type="text/javascript"></script>
</head>
<body>
<style>#forkongithub a {
background: #000;
color: #fff;
text-decoration: none;
font-family: arial, sans-serif;
text-align: center;
box-sizing: content-box;
font-weight: bold;
padding: 5px 40px;
font-size: 1rem;
line-height: 2rem;
position: relative;
transition: 0.5s;
}
#forkongithub a:hover {
background: #c11;
color: #fff;
}
#forkongithub a::before, #forkongithub a::after {
content: "";
width: 100%;
display: block;
position: absolute;
top: 1px;
left: 0;
height: 1px;
background: #fff;
}
#forkongithub a::after {
bottom: 1px;
top: auto;
}
@media screen and (min-width: 0) {
#forkongithub {
position: fixed;
display: block;
top: 0;
left: 0;
width: 200px;
overflow: hidden;
height: 200px;
z-index: 9999;
}
#forkongithub a {
width: 200px;
box-sizing: content-box;
position: absolute;
top: 60px;
left: -60px;
transform: rotate(-45deg);
-webkit-transform: rotate(-45deg);
-ms-transform: rotate(-45deg);
-moz-transform: rotate(-45deg);
-o-transform: rotate(-45deg);
box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.2);
}
}</style>
<span style="box-sizing: content-box;" id="forkongithub"><a style="box-sizing: content-box;"
href="https://github.com/bluejamesbond/Scribe.js"
target="_blank">Fork me on GitHub</a></span>
<div class="header">
<div class="header__logo"></div>
</div>
<div class="notification">
<div class="wrapper">
<div class="text">Completed</div>
</div>
</div>
<div class="content">
<div style="width:100%;height:100%;">
<div class="main">
<div class="important">
<h1>Scribe.js</h1>
<h4>Simple. Clean. Minimal NodeJS logging framework<br/>that is accessible from anywhere in the world.
</h4>
</div>
<div class="normal">
<h2>Demo</h2>
<h5>Enter some text to be printed out. JSON is supported as well. The text will be printed out by the
server using a scribe-enabled console. You can then view the logs to see your content
displayed.</h5>
<div style="width:100%;margin-top:20px;">
<input id="input-tag" style="margin-right:10px;color:#21ce99;" placeholder="Enter tag"/>
<input id="input-msg" style="margin-right:10px;width:565px" placeholder="Enter message"/>
<button id="submit" class="button" href="#">Console.log</button>
</div>
<a class="direct" target="_frame" href="scribe">
<div></div>
<div>Access System Logs</div>
</a>
</div>
<div class="normal">
<h2>Web Access</h2>
<h5>All your logs can be accessed from the web with a rich HTML interface.</h5>
<img alt="WebPanel screenshot" src="img/screenshot-1.png"/>
</div>
</div>
</div>
</div>
</body>
</html>

File diff suppressed because one or more lines are too long

View file

@ -1,54 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<base target="_frame" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>ScribeJS Demo</title>
<link href='https://fonts.googleapis.com/css?family=Open+Sans:400,300,600,700' rel='stylesheet' type='text/css'>
<link href='https://fonts.googleapis.com/css?family=Ubuntu+Mono:400,700' rel='stylesheet' type='text/css'>
<link href="css/style.css" rel="stylesheet" type="text/css">
<script src ="js/jquery-2.1.3.min.js" type="text/javascript"></script>
<script src ="js/index.js" type="text/javascript"></script>
</head>
<body>
<style>#forkongithub a{background:#000;color:#fff;text-decoration:none;font-family:arial,sans-serif;text-align:center;box-sizing: content-box;font-weight:bold;padding:5px 40px;font-size:1rem;line-height:2rem;position:relative;transition:0.5s;}#forkongithub a:hover{background:#c11;color:#fff;}#forkongithub a::before,#forkongithub a::after{content:"";width:100%;display:block;position:absolute;top:1px;left:0;height:1px;background:#fff;}#forkongithub a::after{bottom:1px;top:auto;}@media screen and (min-width:0){#forkongithub{position:fixed;display:block;top:0;left:0;width:200px;overflow:hidden;height:200px;z-index:9999;}#forkongithub a{width:200px;box-sizing: content-box;position:absolute;top:60px;left:-60px;transform:rotate(-45deg);-webkit-transform:rotate(-45deg);-ms-transform:rotate(-45deg);-moz-transform:rotate(-45deg);-o-transform:rotate(-45deg);box-shadow:1px 1px 3px rgba(0,0,0,0.2);}}</style><span style="box-sizing: content-box;" id="forkongithub"><a style="box-sizing: content-box;" href="https://github.com/bluejamesbond/Scribe.js" target="_blank">Fork me on GitHub</a></span>
<div class = "header">
<div class = "header__logo"></div>
</div>
<div class = "notification">
<div class = "wrapper">
<div class = "text">Completed</div>
</div>
</div>
<div class = "content">
<div style="width:100%;height:100%;">
<div class = "main">
<div class = "important">
<h1>Scribe.js</h1>
<h4>Simple. Clean. Minimal NodeJS logging framework<br/>that is accessible from anywhere in the world.</h4>
</div>
<div class = "normal">
<h2>Demo</h2>
<h5>Enter some text to be printed out. JSON is supported as well. The text will be printed out by the server using a scribe-enabled console. You can then view the logs to see your content displayed.</h5>
<div style="width:100%;margin-top:20px;">
<input id="input-tag" style="margin-right:10px;color:#21ce99;" placeholder="Enter tag"/>
<input id="input-msg" style="margin-right:10px;width:565px" placeholder="Enter message"/>
<button id="submit" class="button" href="#">Console.log</button>
</div>
<a class="direct" target="_frame" href="logs">
<div></div>
<div>Access System Logs</div>
</a>
</div>
<div class = "normal">
<h2>Web Access</h2>
<h5>All your logs can be accessed from the web with a rich HTML interface.</h5>
<img alt="WebPanel screenshot" src = "img/screenshot-1.png"/>
</div>
</div>
</div>
</div>
</body>
</html>

View file

@ -1 +1 @@
module.exports = require('./dist/scribe').default;
module.exports = require('./dist/scribe');

View file

@ -1,5 +1,5 @@
{
"main": "http://localhost:4005/scribe/",
"main": "",
"name": "Scribe",
"description": "Scribe logging",
"version": "0.1.0",

View file

@ -1,7 +1,7 @@
{
"name": "scribe-js",
"description": "Comprehensive logging framework with online interface",
"version": "3.0.0-alpha.1",
"version": "3.0.0-alpha.5",
"homepage": "https://github.com/bluejamesbond/Scribe.js",
"keywords": [],
"scripts": {
@ -30,10 +30,23 @@
"main": "index.js",
"license": "MIT",
"dependencies": {
"JSONStream": "1.1.1",
"async": "1.5.2",
"babel-plugin-syntax-async-functions": "6.5.0",
"babel-plugin-syntax-class-properties": "6.5.0",
"babel-plugin-transform-async-functions": "6.5.0",
"babel-plugin-transform-async-to-module-method": "6.7.0",
"babel-plugin-transform-class-properties": "6.5.0",
"babel-plugin-transform-export-default-name": "1.0.4",
"babel-plugin-transform-object-rest-spread": "6.5.0",
"babel-plugin-transform-runtime": "6.5.2",
"babel-preset-es2015": "6.5.0",
"babel-preset-react": "6.5.0",
"babel-register": "6.5.2",
"babel-runtime": "6.5.0",
"basic-auth": "1.0.3",
"basic-auth-connect": "1.0.0",
"bluebird": "3.3.4",
"body-parser": "1.15.0",
"brace": "0.7.0",
"callsite": "1.0.0",
@ -52,9 +65,9 @@
"rc": "1.1.6",
"react": "0.14.7",
"react-ace": "3.1.0",
"react-datepicker": "0.18.0",
"react-datepicker": "0.25.0",
"react-dom": "0.14.7",
"react-influx": "1.0.8",
"react-influx": "1.1.5",
"react-list": "0.7.14",
"react-object-inspector": "0.1.6",
"react-prefixr": "0.1.0",
@ -62,11 +75,7 @@
"socket.io": "1.4.3",
"strip-ansi": "3.0.0",
"superagent": "1.7.2",
"underscore": "1.8.3",
"babel-plugin-transform-object-rest-spread": "6.5.0",
"babel-plugin-transform-runtime": "6.5.2",
"babel-preset-es2015": "6.5.0",
"babel-preset-react": "6.5.0"
"underscore": "1.8.3"
},
"readmeFilename": "README.md",
"devDependencies": {
@ -87,6 +96,7 @@
"license-report": "2.0.0",
"load-grunt-tasks": "3.4.0",
"mocha": "2.4.5",
"time-grunt": "1.3.0"
"time-grunt": "1.3.0",
"trace-error": "0.0.7"
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,5 +1,4 @@
* {
font-family: 'Proxima Nova', "Helvetica", "Helvetic Neue", "Open Sans", sans-serif;
vertical-align: top;
box-sizing: border-box;
outline: none;
@ -30,8 +29,10 @@ code {
"dmenu" ,
"pop" ,
"settings" ,
"calendar" ,
"archive" ,
"console" ,
"arrow" ,
"menu" ;
@each $i in $icons {

View file

@ -0,0 +1,136 @@
@charset "UTF-8";
.checkbox {
width: 13px;
height: 13px;
min-height: 13px;
min-width: 13px;
display: inline-block;
border: 1px solid rgba(255, 255, 255, 0.35);
cursor: pointer;
.light & {
color: #444;
}
.dark & {
color: rgb(63, 200, 41);
}
.light & {
background: rgba(0, 0, 0, 0.05);
border: 1px solid rgba(0, 0, 0, 0.3);
}
.dark & {
background: transparent;
border: 1px solid rgba(255, 255, 255, 0.25);
}
.check {
visibility: hidden;
height: 100%;
width: 100%;
border-radius: 3px;
font-family: 'Fontello', sans-serif;
position: relative;
}
&.checked {
.check {
visibility: visible;
&:before {
font-size: 12px;
content: '';
width: 10px;
height: 5px;
position: absolute;
top: 0;
left: 2px;
border: 3px solid #555;
border-top: none;
border-right: none;
background: transparent;
transform: rotate(-45deg);
.dark & {
border-color: #00FF2A;
}
}
}
}
$toggleSize: 20px;
&.toggle {
width: auto;
height: $toggleSize;
border-radius: $toggleSize;
background: #555;
position: relative;
padding-left: $toggleSize;
border-color: rgba(255, 255, 255, 0.1);
transition: padding 400ms, background 400ms;
.light & {
background: #A3AAAF;
border-color: rgba(0, 0, 0, 0.15);
}
> .label {
font-size: 9px;
display: block;
padding: 0 10px;
text-transform: uppercase;
font-weight: bold;
color: #bbb;
line-height: $toggleSize - 1px;
letter-spacing: 1px;
transition: color 400ms;
.light & {
color: #eee;
}
}
.check {
visibility: visible;
background: #DDD;
border-radius: $toggleSize;
width: $toggleSize + 4px;
height: $toggleSize + 4px;
position: absolute;
top: -3px;
left: 0;
margin-left: -2px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(255, 255, 255, 0.15);
transition: left 400ms, margin-left 400ms;
.light & {
background: #fff;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
}
}
&.checked {
padding-left: 0;
padding-right: $toggleSize;
background: #6EC53A;
.label {
color: #fff;
}
.check {
left: 100%;
margin-left: -$toggleSize - 2px;
&:before {
content: none;
border: none;
}
}
}
}
}

View file

@ -1,12 +1,12 @@
$color: #de930f;
$secondaryColor: #ae720c;
.tether-element-attached-top .datepicker__triangle, .tether-element-attached-bottom .datepicker__triangle, .datepicker__year-read-view--down-arrow {
.react-datepicker__tether-element-attached-top .react-datepicker__triangle, .react-datepicker__tether-element-attached-bottom .react-datepicker__triangle, .react-datepicker__year-read-view--down-arrow {
margin-left: -8px;
position: absolute;
}
.tether-element-attached-top .datepicker__triangle, .tether-element-attached-bottom .datepicker__triangle, .datepicker__year-read-view--down-arrow, .tether-element-attached-top .datepicker__triangle::before, .tether-element-attached-bottom .datepicker__triangle::before, .datepicker__year-read-view--down-arrow::before {
.react-datepicker__tether-element-attached-top .react-datepicker__triangle, .react-datepicker__tether-element-attached-bottom .react-datepicker__triangle, .react-datepicker__year-read-view--down-arrow, .react-datepicker__tether-element-attached-top .react-datepicker__triangle::before, .react-datepicker__tether-element-attached-bottom .react-datepicker__triangle::before, .react-datepicker__year-read-view--down-arrow::before {
box-sizing: content-box;
position: absolute;
border: 8px solid transparent;
@ -14,7 +14,7 @@ $secondaryColor: #ae720c;
width: 1px;
}
.tether-element-attached-top .datepicker__triangle::before, .tether-element-attached-bottom .datepicker__triangle::before, .datepicker__year-read-view--down-arrow::before {
.react-datepicker__tether-element-attached-top .react-datepicker__triangle::before, .react-datepicker__tether-element-attached-bottom .react-datepicker__triangle::before, .react-datepicker__year-read-view--down-arrow::before {
content: "";
z-index: -1;
border-width: 8px;
@ -22,37 +22,37 @@ $secondaryColor: #ae720c;
border-bottom-color: transparent;
}
.tether-element-attached-top .datepicker__triangle {
.react-datepicker__tether-element-attached-top .react-datepicker__triangle {
top: 0;
margin-top: -8px;
}
.tether-element-attached-top .datepicker__triangle, .tether-element-attached-top .datepicker__triangle::before {
.react-datepicker__tether-element-attached-top .react-datepicker__triangle, .react-datepicker__tether-element-attached-top .react-datepicker__triangle::before {
border-top: none;
border-bottom-color: $secondaryColor;
}
.tether-element-attached-top .datepicker__triangle::before {
.react-datepicker__tether-element-attached-top .react-datepicker__triangle::before {
top: -1px;
border-bottom-color: transparent;
}
.tether-element-attached-bottom .datepicker__triangle, .datepicker__year-read-view--down-arrow {
.react-datepicker__tether-element-attached-bottom .react-datepicker__triangle, .react-datepicker__year-read-view--down-arrow {
bottom: 0;
margin-bottom: -8px;
}
.tether-element-attached-bottom .datepicker__triangle, .datepicker__year-read-view--down-arrow, .tether-element-attached-bottom .datepicker__triangle::before, .datepicker__year-read-view--down-arrow::before {
.react-datepicker__tether-element-attached-bottom .react-datepicker__triangle, .react-datepicker__year-read-view--down-arrow, .react-datepicker__tether-element-attached-bottom .react-datepicker__triangle::before, .react-datepicker__year-read-view--down-arrow::before {
border-bottom: none;
border-top-color: #fff;
}
.tether-element-attached-bottom .datepicker__triangle::before, .datepicker__year-read-view--down-arrow::before {
.react-datepicker__tether-element-attached-bottom .react-datepicker__triangle::before, .react-datepicker__year-read-view--down-arrow::before {
bottom: -1px;
border-top-color: transparent;
}
.datepicker {
.react-datepicker {
font-size: 11px;
background-color: $color;
color: #000;
@ -62,29 +62,29 @@ $secondaryColor: #ae720c;
box-shadow: 0 0 40px rgba(0, 0, 0, 0.37);
}
.datepicker__container {
.react-datepicker__container {
position: absolute;
display: inline-block;
z-index: 2147483647;
}
.datepicker__triangle {
.react-datepicker__triangle {
position: absolute;
left: 50px;
}
.tether-target-attached-top.datepicker__container {
.react-datepicker__tether-target-attached-top.react-datepicker__container {
margin-top: -20px;
}
.datepicker__header {
.react-datepicker__header {
text-align: center;
background-color: $secondaryColor;
padding-top: 10px;
position: relative;
}
.datepicker__current-month {
.react-datepicker__current-month {
margin-top: 0;
color: #FFF;
font-weight: bold;
@ -96,11 +96,11 @@ $secondaryColor: #ae720c;
display: inline-block;
}
.datepicker__current-month--hasYearDropdown {
.react-datepicker__current-month--hasYearDropdown {
margin-bottom: 16px;
}
.datepicker__navigation {
.react-datepicker__navigation {
line-height: 24px;
text-align: center;
cursor: pointer;
@ -110,25 +110,25 @@ $secondaryColor: #ae720c;
border: 6px solid transparent;
}
.datepicker__navigation--previous {
.react-datepicker__navigation--previous {
left: 10px;
border-right-color: #fff;
}
.datepicker__navigation--previous:hover {
.react-datepicker__navigation--previous:hover {
border-right-color: #b3b3b3;
}
.datepicker__navigation--next {
.react-datepicker__navigation--next {
right: 10px;
border-left-color: #fff;
}
.datepicker__navigation--next:hover {
.react-datepicker__navigation--next:hover {
border-left-color: #b3b3b3;
}
.datepicker__navigation--years {
.react-datepicker__navigation--years {
position: relative;
top: 0;
display: block;
@ -136,39 +136,39 @@ $secondaryColor: #ae720c;
margin-right: auto;
}
.datepicker__navigation--years-previous {
.react-datepicker__navigation--years-previous {
top: 4px;
border-top-color: #ccc;
}
.datepicker__navigation--years-previous:hover {
.react-datepicker__navigation--years-previous:hover {
border-top-color: #b3b3b3;
}
.datepicker__navigation--years-upcoming {
.react-datepicker__navigation--years-upcoming {
top: -4px;
border-bottom-color: #ccc;
}
.datepicker__navigation--years-upcoming:hover {
.react-datepicker__navigation--years-upcoming:hover {
border-bottom-color: #b3b3b3;
}
.datepicker__week-day {
.react-datepicker__week-day {
color: #ccc;
display: inline-block;
width: 28px;
line-height: 24px;
}
.datepicker__month {
.react-datepicker__month {
margin: 5px;
text-align: center;
background: $color;
}
.datepicker__day {
color: rgba(255, 255, 255, 0.44);
.react-datepicker__day {
color: rgba(255, 255, 255, 0.6);
display: inline-block;
width: 24px;
line-height: 21px;
@ -182,44 +182,45 @@ $secondaryColor: #ae720c;
border-radius: 24px;
}
.datepicker__day:hover {
.react-datepicker__day:hover {
/* border-radius: 4px; */
background-color: #f0f0f0;
/* color: $color; */
color: $color;
}
.datepicker__day--today {
.react-datepicker__day--today {
/* font-weight: bold; */
}
.datepicker__day--selected, .datepicker__day--in-range {
.react-datepicker__day--selected, .react-datepicker__day--in-range {
/* border-radius: 4px; */
background-color: rgba(255, 255, 255, 0.43);
color: #fff;
font-weight: bold;
}
.datepicker__day--selected:hover, .datepicker__day--in-range:hover {
.react-datepicker__day--selected:hover, .react-datepicker__day--in-range:hover {
/* background-color: #1d5d90; */
/* color: red; */
color: $color;
}
.datepicker__day--disabled {
.react-datepicker__day--disabled {
cursor: default;
color: #ccc;
color: rgba(255, 255, 255, 0.15) !important;
}
.datepicker__day--disabled:hover {
.react-datepicker__day--disabled:hover {
background-color: transparent;
}
.datepicker__input-container {
.react-datepicker__input-container {
display: inline-block;
position: relative;
}
.datepicker__input {
.react-datepicker__input {
position: relative;
font-size: 13px;
border-radius: 4px;
@ -229,17 +230,17 @@ $secondaryColor: #ae720c;
padding: 6px 10px 5px;
}
.datepicker__input:focus {
.react-datepicker__input:focus {
outline: none;
border-color: #aeaeae;
box-shadow: inset 0 2px 2px #e9e9e9, 0 0 10px 0 rgba(73, 107, 125, 0.3);
}
.datepicker__input:not(:valid) ~ .close-icon {
.react-datepicker__input:not(:valid) ~ .close-icon {
display: none;
}
.datepicker__year-read-view {
.react-datepicker__year-read-view {
width: 50%;
left: 25%;
position: absolute;
@ -248,15 +249,15 @@ $secondaryColor: #ae720c;
border-radius: 4px;
}
.datepicker__year-read-view:hover {
.react-datepicker__year-read-view:hover {
cursor: pointer;
}
.datepicker__year-read-view:hover .datepicker__year-read-view--down-arrow {
.react-datepicker__year-read-view:hover .react-datepicker__year-read-view--down-arrow {
border-top-color: #b3b3b3;
}
.datepicker__year-read-view--down-arrow {
.react-datepicker__year-read-view--down-arrow {
border-top-color: #ccc;
margin-bottom: 3px;
left: 5px;
@ -265,12 +266,12 @@ $secondaryColor: #ae720c;
border-width: 6px;
}
.datepicker__year-read-view--selected-year {
.react-datepicker__year-read-view--selected-year {
right: 6px;
position: relative;
}
.datepicker__year-dropdown {
.react-datepicker__year-dropdown {
background-color: #f0f0f0;
position: absolute;
width: 50%;
@ -281,11 +282,11 @@ $secondaryColor: #ae720c;
border: 1px solid #aeaeae;
}
.datepicker__year-dropdown:hover {
.react-datepicker__year-dropdown:hover {
cursor: pointer;
}
.datepicker__year-option {
.react-datepicker__year-option {
line-height: 20px;
width: 100%;
display: block;
@ -293,12 +294,12 @@ $secondaryColor: #ae720c;
margin-right: auto;
}
.datepicker__year-option:first-of-type {
.react-datepicker__year-option:first-of-type {
border-top-left-radius: 4px;
border-top-right-radius: 4px;
}
.datepicker__year-option:last-of-type {
.react-datepicker__year-option:last-of-type {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
@ -307,25 +308,30 @@ $secondaryColor: #ae720c;
border-bottom-right-radius: 4px;
}
.datepicker__year-option:hover {
.react-datepicker__year-option:hover {
background-color: #ccc;
}
.datepicker__year-option:hover .datepicker__navigation--years-upcoming {
.react-datepicker__year-option:hover .react-datepicker__navigation--years-upcoming {
border-bottom-color: #b3b3b3;
}
.datepicker__year-option:hover .datepicker__navigation--years-previous {
.react-datepicker__year-option:hover .react-datepicker__navigation--years-previous {
border-top-color: #b3b3b3;
}
.datepicker__year-option--selected {
.react-datepicker__year-option--selected {
position: absolute;
left: 30px;
}
.datepicker__header .datepicker__day {
.react-datepicker__header .react-datepicker__day {
background: transparent !important;
color: #FFF !important;
cursor: default;
font-weight: bold;
}
.react-datepicker__tether-element {
z-index: 9999;
}

File diff suppressed because one or more lines are too long

View file

@ -4,19 +4,37 @@
@import "shared/scroll";
@import "shared/layouts";
@import "shared/datepicker";
@import "shared/checkbox";
html, body {
background: #222;
font-size: 0.95rem;
line-height: 1.5rem;
font-family: 'Proxima Nova', "Helvetica", "Helvetic Neue", "Open Sans", sans-serif;
&.blue {
background: #242831;
}
transition: background 1500ms;
}
.animate-height {
transition: max-height 600ms, min-height 600ms, height 600ms;
}
.visibility-hidden {
visibility: hidden;
opacity: 0;
}
.animate-visibility {
transition: visibility 600ms, opacity 600ms;
}
.header {
width: 100%;
height: 50px;
min-height: 50px;
background: #222;
box-shadow: 0 1px 10px #111;
height: 125px;
min-height: 125px;
position: relative;
z-index: 3;
}
@ -24,12 +42,11 @@ html, body {
.group {
.title {
text-transform: uppercase;
color: #444;
color: #E4E4E4;
font-weight: bold;
height: 50px;
line-height: 50px;
letter-spacing: 1px;
border-bottom: 1px solid #ddd;
padding-left: 20px;
font-size: 80%;
}
@ -51,7 +68,7 @@ html, body {
.sidebar {
width: 200px;
min-width: 170px;
background: #fff;
background: rgba(0, 0, 0, .5);
}
.bgRed {
@ -91,7 +108,7 @@ html, body {
text-transform: uppercase;
color: #CF358C;
&.persistent {
color: #32BBC7;
color: rgb(155, 187, 220);
}
&.stroke {
font-size: 9px;
@ -107,25 +124,24 @@ html, body {
border-radius: 2px;
letter-spacing: 1px;
font-size: 8px;
&.new-entry {
background: #3F51B5;
}
}
}
.pre {
margin-right: 3px;
@extend .monospaced;
font-size: 85%;
font-weight: normal;
}
.yellow {
color: #D4D400;
color: rgb(93, 176, 215);
}
.entry {
font-size: 80%;
line-height: 130%;
line-height: 110%;
padding: 3px 0;
white-space: pre;
font-family: Meslo, Menlo, Monaco, Consolas, monospace;
font-size: 11px;
}
.line {
@ -133,7 +149,6 @@ html, body {
text-align: right;
display: inline-block;
margin-right: 5px;
@extend .monospaced;
font-size: 80%;
}
@ -172,7 +187,7 @@ html, body {
.ObjectInspector {
cursor: default;
display: block;
color: #FFF;
color: rgb(189, 198, 207);
}
.ObjectInspector-unselectable {
@ -204,7 +219,7 @@ html, body {
}
.ObjectInspector-object-name {
color: #26F714;
color: rgb(227, 110, 236);
}
.ObjectInspector-object-value-null, .ObjectInspector-object-value-undefined {
@ -212,7 +227,7 @@ html, body {
}
.ObjectInspector-object-value-string {
color: #525252;
color: rgb(242, 151, 102);
}
.ObjectInspector-object-value-symbol {
@ -306,7 +321,7 @@ html, body {
font-size: 10px;
width: 60px;
max-width: 60px;
background: #444;
background: rgba(255, 255, 255, 0.1);
color: #999;
letter-spacing: 1px;
border-radius: $fieldRadius 0 0 $fieldRadius;
@ -316,19 +331,30 @@ html, body {
text-transform: uppercase;
}
.input {
background: #272822;
background: transparent;
border-radius: 0 $fieldRadius $fieldRadius 0;
padding: 5px 5px 4px;
border: 1px solid #444;
padding: 6px 5px 4px;
border: 1px solid rgba(255, 255, 255, 0.1);
* {
font-size: 10px;
}
}
&.readonly {
.input {
border: transparent;
background: rgba(255, 255, 255, 0.1);
}
}
}
.ace-monokai {
background: transparent !important;
}
.button {
background: #2589DE;
border-radius: 20px;
border-radius: 2px;
color: #FFF;
font-size: 70%;
text-transform: uppercase;
@ -339,6 +365,8 @@ html, body {
transition: background 50ms;
height: 26px;
line-height: 26px;
display: block;
text-decoration: none;
&.disabled {
cursor: none;
pointer-events: none;
@ -360,4 +388,204 @@ html, body {
.bgPurple {
background: #6C07B9 !important;
}
.big-input {
letter-spacing: 1px;
font-size: 15px;
width: 100%;
border: none;
color: #FFF;
outline: none;
margin: 0;
font-weight: 100;
padding: 17px 17px 5px;
background: transparent;
}
.ggroup {
padding: 0 2px;
border-left: 1px solid rgba(148, 148, 148, 0.07);
transition: background 1200ms;
&:first-child {
border-left: none;
}
.gg {
justify-content: center;
}
}
.bar {
background: #333;
position: relative;
max-width: 3px;
margin-right: 2px;
min-width: 1px;
transition: height 300ms, top 300ms, background 300ms;
&:last-child {
margin-right: 0;
}
&.log {
background: green;
}
&.error {
background: red;
}
&.info {
background: cyan;
}
&.debug {
background: yellow;
}
&.trace {
background: magenta;
}
&.express {
background: #423eff;
}
}
.ObjectInspector-object-preview {
.ObjectInspector-object-value-string {
max-height: 24px;
text-overflow: ellipsis;
overflow: hidden;
display: inline-block;
}
}
.checkbox {
margin: 0 8px 0 0;
border-radius: 2px;
background: #444;
border: none;
.check:before {
border-color: rgb(146, 247, 29) !important;
}
}
.red-selected {
background: rgba(255, 0, 0, 0.3);
}
.time-series-label {
position: absolute;
left: 3px;
right: 3px;
top: 5px;
font-size: 8px;
text-align: center;
color: rgba(255, 255, 255, 0.1);
letter-spacing: 1px;
line-height: 10px;
pointer-events: none;
}
.date-holder {
height: 14px;
position: relative;
top: -1px;
cursor: pointer;
}
.react-datepicker__input-container {
input {
display: inline-block;
border-radius: 2px;
color: #FFF;
text-transform: uppercase;
letter-spacing: 1px;
font-weight: normal;
font-size: 9px !important;
line-height: 14px;
margin-right: 5px;
background: rgba(255, 255, 255, 0.16);
outline: none;
border: none;
padding: 0 15px 0 5px;
}
}
.react-datepicker__input-container::after {
font-family: 'Fontello';
content: '';
position: absolute;
right: 8px;
font-size: 80%;
top: 0;
pointer-events: none;
line-height: 14px;
color: rgba(255, 255, 255, 0.41);
}
.date {
}
.date-to {
line-height: 13px;
margin-right: 6px;
color: rgba(255, 255, 255, 0.28);
}
.tooltip {
position: relative;
}
.tooltip .tooltiptext {
visibility: hidden;
width: 120px;
background-color: black;
color: #fff;
text-align: center;
padding: 5px 0;
border-radius: 3px;
position: absolute;
font-size: 11px;
font-weight: bold;
letter-spacing: 1px;
opacity: 0;
transition: opacity 1s;
overflow: hidden;
text-overflow: ellipsis;
text-transform: uppercase;
z-index: 99999;
line-height: 11px;
border: 1px solid #222;
}
.tooltip:hover .tooltiptext {
visibility: visible;
opacity: 1;
}
.tooltip-top {
bottom: 125%;
left: 50%;
margin-left: -60px;
}
a {
text-decoration: none;
}
.arrow-expand {
position: absolute;
right: 0px;
cursor: pointer;
top: 0px;
bottom: -10px;
width: 40px;
text-align: center;
color: rgba(255, 255, 255, 1);
opacity: 0.5;
line-height: 50px;
transition: opacity 300ms, transform 300ms;
transform-origin: 50% 50%;
&.rotate-down {
transform: rotate(-180deg);
}
}
.arrow-expand:hover {
opacity: 1;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 645 KiB

After

Width:  |  Height:  |  Size: 228 KiB

View file

@ -0,0 +1,42 @@
var MongoClient = require('mongodb').MongoClient;
var url = 'mongodb://localhost:27017/scribe';
MongoClient.connect(url, function (err, db) {
if (err) return console.error(err);
var Entry = db.collection('entries');
var cursor = Entry.find({serialized: {$exists: false}});
cursor.on('data', function (doc) {
if (doc.serialized) {
return cursor.resume();
}
cursor.pause();
Entry.update({_id: doc._id},
{$set: {serialized: JSON.stringify(doc)}},
err => {
if (err) {
console.error(err);
cursor.close();
return;
}
cursor.resume();
});
});
cursor.on('error', function (err) {
console.error(err);
process.exit(1);
});
cursor.on('end', function () {
db.close();
console.log('Finished. Exiting...');
process.exit(0);
});
});

View file

@ -29,8 +29,78 @@ export function parse(str, reviver) {
});
}
const stringifyOnce = function (obj, replacer, indent) {
var printedObjects = [];
var printedObjectKeys = [];
function printOnceReplacer(key, value) {
if (printedObjects.length > 2000) { // browsers will not print more than 20K, I don't see the point to allow 2K.. algorithm will not be fast anyway if we have too many objects
return 'object too long';
}
var printedObjIndex = false;
printedObjects.forEach(function (obj, index) {
if (obj === value) {
printedObjIndex = index;
}
});
if (key == '') { //root element
printedObjects.push(obj);
printedObjectKeys.push("root");
return value;
}
else if (printedObjIndex + "" != "false" && typeof(value) == "object") {
if (printedObjectKeys[printedObjIndex] == "root") {
return "(pointer to root)";
} else {
return "(see " + ((!!value && !!value.constructor) ? value.constructor.name.toLowerCase() : typeof(value)) + " with key " + printedObjectKeys[printedObjIndex] + ")";
}
} else {
var qualifiedKey = key || "(empty key)";
printedObjects.push(value);
printedObjectKeys.push(qualifiedKey);
if (replacer) {
return replacer(key, value);
} else {
return value;
}
}
}
return JSON.stringify(obj, printOnceReplacer, indent);
};
// http://www.z-car.com/blog/programming/how-to-escape-mongo-keys-using-node-js-in-a-flash
function escapeKeys(obj) {
if (!(Boolean(obj) && typeof obj == 'object'
&& Object.keys(obj).length > 0)) {
return false;
}
Object.keys(obj).forEach(function (key) {
if (typeof(obj[key]) == 'object') {
escapeKeys(obj[key]);
} else {
if (key.indexOf('.') !== -1) {
var newkey = key.replace(/\./g, '_dot_');
obj[newkey] = obj[key];
delete obj[key];
}
if (key.indexOf('$') !== -1) {
var newkey = key.replace(/\$/g, '_amp_');
obj[newkey] = obj[key];
delete obj[key];
}
}
});
return true;
}
export function stringify(obj, replacer, spaces) {
return JSON.stringify(obj, (k, v)=> {
const objStr = stringifyOnce(obj, (k, v)=> {
if (typeof v !== 'undefined' && v !== null) {
if (typeof v.then === 'function') {
return 'Promise { <pending> }'
@ -43,4 +113,10 @@ export function stringify(obj, replacer, spaces) {
return v;
}, spaces);
const objCpy = JSON.parse(objStr);
escapeKeys(objCpy);
return JSON.stringify(objCpy);
}

8
src/libs/time.js Normal file
View file

@ -0,0 +1,8 @@
export default function time() {
if (typeof process !== 'undefined' && typeof process.hrtime === 'function') {
const duration = process.hrtime();
return duration[0] * 1000 + duration[1] / 1e6;
}
return Date.now();
}

View file

@ -0,0 +1,29 @@
import onFinish from 'on-finished'
import time from '../libs/time';
export default class ExpressLogger {
constructor(scribe) {
this.options = scribe.module('middleware/ExpressLogger').options;
this.scribe = scribe;
}
getMiddleware() {
const {scribe, options} = this;
return (req, res, next) => {
let start = time();
onFinish(res, () => {
const {originalUrl} = req;
for (const k of options.ignore) {
if (new RegExp(k).test(originalUrl)) {
return;
}
}
scribe[options.expose]({req, res, duration: (time() - start)});
});
next();
}
}
}

1
src/middleware/index.js Normal file
View file

@ -0,0 +1 @@
export {default as ExpressLogger} from './ExpressLogger';

View file

@ -1,15 +1,6 @@
import Console from './Console'
import stack from 'callsite'
import onFinish from 'on-finished'
function time() {
if (typeof process !== 'undefined' && typeof process.hrtime === 'function') {
const duration = process.hrtime();
return duration[0] * 1000 + duration[1] / 1e6;
}
return Date.now();
}
import time from '../libs/time';
export default class extends Console {
constructor(app, id) {
@ -37,25 +28,6 @@ export default class extends Console {
return this;
}
middleware(expose = 'express', ignore = [/(scribe)/g]) {
this.expose(expose);
ignore = Array.isArray(ignore) ? ignore : [ignore];
return (req, res, next) => {
let start = time();
onFinish(res, () => {
const {originalUrl} = req;
for (const k of ignore) {
if (new RegExp(k).test(originalUrl)) return;
}
this[expose]({req, res, duration: (time() - start)});
});
next();
}
}
tag(...tags) {
this.transient('tags', tags);
@ -74,7 +46,7 @@ export default class extends Console {
}
trace(text) {
this.transient('callsite', this.callSite(4));
this.transient('callsite', this.callSite(2));
return this.out('trace', new Error(text));
}
@ -84,9 +56,9 @@ export default class extends Console {
throw new Error(`No such label: ${label}`);
}
this._times.delete(label);
this.captureStackTrace();
this.transient('callsite', this.callSite(5));
this._times.delete(label);
const duration = time() - start;

View file

@ -2,14 +2,27 @@ import stack from 'callsite'
import async from 'async'
export default class {
constructor(app = 'Scribe', id = process.pid) {
constructor(config) {
this._transient = {};
this._persistent = {};
this._pipelines = {};
this._exposed = {};
this._config = config;
this.persistent('app', app);
this.persistent('id', id);
this.persistent('app', this._config.app);
this.persistent('id', this._config.id);
}
module(namespace) {
if (namespace) {
const options = this._config.module[namespace] || {};
options.isSet = () => Object.keys(options).length;
return {options};
}
return this._config;
}
transient(key, value) {
@ -30,18 +43,28 @@ export default class {
return this;
}
exposed() {
exposed(expose) {
if (expose) {
return !!this._exposed[expose];
}
return Object.keys(this._exposed);
}
callSite(idx = 5) {
captureStackTrace(idx = 3) {
if (!this.transient('callsite')) {
return this.transient('callsite', this.callSite(idx));
}
}
callSite(idx = 3) {
try {
const site = stack()[idx];
return {
line: site.getLineNumber(),
file: site.getFileName(),
func: site.getFunctionName()
func: site.getFunctionName() || 'anonymous'
};
return site;
@ -55,6 +78,8 @@ export default class {
}
out(expose, ...args) {
this.captureStackTrace();
return new Promise((resolve, reject)=> {
if (!this._pipelines[expose]) {
return resolve();

2
src/reader/index.js Normal file
View file

@ -0,0 +1,2 @@
export {default as BasicConsole} from './BasicConsole';
export {default as Console} from './Console';

1
src/router/index.js Normal file
View file

@ -0,0 +1 @@
export {default as Viewer} from './Viewer';

185
src/router/viewer.js Normal file
View file

@ -0,0 +1,185 @@
import express, {Router} from 'express';
import mongoose from 'mongoose';
import jade from 'jade';
import basicAuth from 'basic-auth-connect';
import bodyParser from 'body-parser'
import JSONStream from 'JSONStream';
import NwBuilder from 'nw-builder';
import nativePackage from '../../native/package.json';
import {format} from 'url';
import {EntrySchema} from '../writer/MongoDB';
import extend from 'extend';
import fs from 'fs';
function getObject(d, def) {
if (typeof d === 'undefined' || d === null) {
return def || {};
} else if (typeof d === 'object') {
return d;
} else {
try {
return JSON.parse(d);
} catch (e) {
return def || {};
}
}
}
const viewer = jade.compileFile(`${__dirname}/../../views/viewer.jade`);
const login = jade.compileFile(`${__dirname}/../../views/login.jade`);
export default class Viewer {
constructor(scribe) {
this.scribe = scribe;
}
getRouter() {
const scribe = this.scribe;
const routerOptions = scribe.module('router/Viewer').options;
const clientOptions = scribe.module('router/Viewer/client').options;
let conn, Entry;
mongoose.set('debug', routerOptions.debug);
conn = mongoose.createConnection(routerOptions.mongoUri);
Entry = conn.model('Entry', EntrySchema);
const router = new Router();
if (routerOptions.username && routerOptions.password) {
router.use(basicAuth(routerOptions.username, routerOptions.password));
}
router.use(express.static(`${__dirname}/../../public`));
if (routerOptions.useBodyParser) {
router.use(bodyParser.json());
}
const render = (req, res)=> res.send(viewer({config: JSON.stringify(clientOptions)}));
router.get('/', render);
router.get('/viewer', render);
router.post('/rest/timeseries', (req, res) => {
try {
if (req.body && req.body.date) {
req.body.date = req.body.date || {};
if (req.body.date.$gt) req.body.date.$gt = new Date(req.body.date.$gt);
if (req.body.date.$gte) req.body.date.$gte = new Date(req.body.date.$gte);
if (req.body.date.$eq) req.body.date.$eq = new Date(req.body.date.$eq);
if (req.body.date.$lt) req.body.date.$lt = new Date(req.body.date.$lt);
if (req.body.date.$lte) req.body.date.$lte = new Date(req.body.date.$lte);
if (req.body._id && Array.isArray(req.body._id.$in)) req.body._id.$in = req.body._id.$in.map(a => mongoose.Types.ObjectId(a));
if (req.body._id && typeof req.body._id === 'string') req.body._id = mongoose.Types.ObjectId(req.body._id);
}
} catch (e) {
// ignore
}
Entry.aggregate(
[
{$match: req.body},
{
$project: {
hour: {
year: {$year: '$date'},
month: {$month: '$date'},
day: {$dayOfMonth: '$date'},
hour: {$hour: '$date'}
},
expose: '$expose'
}
},
{
$group: {
_id: {hour: '$hour', expose: '$expose'},
number: {$sum: 1}
}
}
], (err, out) => res.json(out || []));
});
router.get('/rest/db/:collection', (req, res)=> {
if (!routerOptions.mongoUri) {
return res.json({err: 0, docs: []});
}
var selector = getObject(req.query.selector);
var fields = typeof req.query.fields === 'string' ? req.query.fields : '';
var sort = getObject(req.query.sort, {_id: -1});
var limit = !isNaN(req.query.limit) ? Math.max(0, parseInt(req.query.limit)) : Number.MAX_SAFE_INTEGER;
var skip = !isNaN(req.query.skip) ? Math.max(0, parseInt(req.query.skip)) : 0;
res.type('application/json');
const stream = Entry.find(selector)
.select(...fields.split(' '))
.skip(skip)
.limit(limit)
.sort(sort)
.stream();
stream
.pipe(JSONStream.stringify())
.pipe(res);
});
router.delete('/rest/db/:collection', (req, res)=> {
if (!routerOptions.mongoUri) {
res.status(410);
return res.send();
}
var ids = req.query.id;
try {
ids = JSON.parse(ids);
} catch (e) {
// ignore
}
if (!Array.isArray(ids)) {
ids = [req.param('id')];
}
Entry.remove({_id: {$in: ids}}, err => {
res.status(err ? 410 : 200);
res.send();
});
});
if (routerOptions.native) {
}
return router;
}
getNative() {
const scribe = this.scribe;
const nativeOptions = scribe.module('router/Viewer/native').options;
// update
nativePackage.main = format(nativeOptions);
// save
fs.writeFileSync(`${__dirname}/../../native/package.json`,
JSON.stringify(nativePackage, null, 4), {encoding: 'utf8'});
const nw = new NwBuilder(extend(true, {
platforms: ['win', 'osx', 'linux'],
buildDir: `${__dirname}/../../public/native`,
version: '0.12.3',
zip: true
}, nativeOptions, {files: `${__dirname}/../../native/**/**`}));
if (nativeOptions.debug) {
nw.on('log', d => console.log(d));
}
return nw.build();
}
}

View file

@ -1,135 +0,0 @@
import express, {Router} from 'express'
import mongoose from 'mongoose'
import EntrySchema from '../schemas/entry'
import jade from 'jade'
import basicAuth from 'basic-auth';
import bodyParser from 'body-parser'
function getObject(d, def) {
if (typeof d === 'undefined' || d === null) {
return def || {};
} else if (typeof d === 'object') {
return d;
} else {
try {
return JSON.parse(d);
} catch (e) {
return def || {};
}
}
}
const viewer = jade.compileFile(`${__dirname}/../../views/viewer.jade`);
const login = jade.compileFile(`${__dirname}/../../views/login.jade`);
export function create(mongoUri = 'mongodb://localhost/scribe', routerConfig = {}, clientConfig = {}, debug = false) {
routerConfig = Object.assign({
useBodyParser: true,
username: 'build',
password: 'build'
}, routerConfig);
let conn, Entry;
if (mongoUri) {
mongoose.set('debug', debug);
conn = mongoose.createConnection(mongoUri);
Entry = conn.model('Entry', EntrySchema);
}
const router = new Router();
var authenticate = function (req, res, next) {
function unauthorized(res) {
res.set('WWW-Authenticate', 'Basic realm=Authorization Required');
return res.sendStatus(401);
}
if (routerConfig.authentication === false || (!routerConfig.username && !routerConfig.password)) {
return next();
}
var user = basicAuth(req);
if (!user || !user.name || !user.pass) {
return unauthorized(res);
}
if (user.name === routerConfig.username && user.pass === routerConfig.password) {
return next();
} else {
return unauthorized(res);
}
};
router.use(authenticate);
router.use(express.static(`${__dirname}/../../public`));
if (routerConfig.useBodyParser) {
router.use(bodyParser.json());
}
const renderViewer = (req, res)=> res.send(viewer({config: JSON.stringify(clientConfig)}));
router.get('/', renderViewer);
router.get('/viewer', renderViewer);
router.get('/rest/:collection', (req, res)=> {
if (!mongoUri) {
return res.json({err: 0, docs: []});
}
var collection = req.params.collection;
var selector = getObject(req.query.selector);
var fields = typeof req.query.fields === 'string' ? req.query.fields : '';
var sort = getObject(req.query.sort, {_id: -1});
var limit = !isNaN(req.query.limit) ? Math.max(0, parseInt(Number(req.query.limit))) : Number.MAX_SAFE_INTEGER;
var col = Entry; // defaulting to Entry for now
if (!col) {
return res.json({err: 1, docs: []});
}
col.find(selector)
.select(fields)
.sort(sort)
.limit(limit)
.lean()
.exec((err = 0, docs = []) => res.json({err, docs}));
});
router.delete('/rest/:collection', (req, res)=> {
if (!mongoUri) {
res.status(410);
return res.send();
}
var collection = rreq.params.collection;
var ids = req.query.id;
try {
ids = JSON.parse(ids);
} catch (e) {
// ignore
}
if (!Array.isArray(ids)) {
ids = [req.param('id')];
}
var col = Entry; // defaulting to Entry for now
if (col) {
return col.remove({_id: {$in: ids}}, err => {
res.status(err ? 410 : 200);
res.send();
});
}
res.status(410);
res.send();
});
return router;
}

View file

@ -1,14 +0,0 @@
import {Schema} from 'mongoose'
const EntrySchema = new Schema({
transient: Schema.Types.Object,
persistent: Schema.Types.Object,
args: Schema.Types.Object,
date: {type: Date, default: Date.now},
expose: String,
serialized: String // experimental
});
EntrySchema.index({'persistent.app': 1, 'persistent.id': -1});
export default EntrySchema;

View file

@ -1,136 +1,64 @@
import fs from 'fs'
import path from 'path'
import Console from './readers/Console'
import BasicConsole from './readers/BasicConsole'
import Inspector from './transforms/Inspector'
import ExpressInspector from './transforms/ExpressInspector'
import ExpressExtractor from './transforms/ExpressExtractor'
import JSON2Converter from './transforms/JSON2Converter'
import ErrorExtractor from './transforms/ErrorExtractor'
import MongoDB from './writers/MongoDB'
import SocketIO from './writers/SocketIO'
import DefaultConsole from './writers/DefaultConsole'
import {create} from './routers/viewer'
import NwBuilder from 'nw-builder'
import fs from 'fs';
import path from 'path';
import rc from 'rc'
import nativePackage from './../native/package.json'
import extend from 'extend'
import extend from 'extend';
export const Writer = {MongoDB, DefaultConsole};
export const Reader = {BasicConsole, Console};
export const Transform = {Inspector, ExpressInspector, ExpressExtractor, ErrorExtractor, JSON2Converter};
import * as Middleware from './middleware';
import * as Reader from './reader';
import * as Router from './router';
import * as Transform from './transform';
import * as Writer from './writer';
const defaultOpts = {
name: 'Scribe',
mongoUri: 'mongodb://localhost/scribe',
mongo: true,
basePath: 'scribe/',
socketPort: 4000,
socket: true,
inspector: {
colors: true,
showHidden: false,
depth: 5,
pre: true,
callsite: true,
tags: true,
args: true,
metrics: true
},
web: {
router: {
username: 'build',
password: 'build',
authentication: true,
sessionSecret: 'scribe-session',
useBodyParser: true,
useSession: true
},
client: {
socketPorts: [4000],
exposed: {
all: {label: 'all', query: {expose: {$exists: true}}},
//error: {label: 'error', query: {expose: 'error'}},
//express: {label: 'express', query: {expose: 'express'}},
//info: {label: 'info', query: {expose: 'info'}},
//log: {label: 'log', query: {expose: 'log'}},
//warn: {label: 'warn', query: {expose: 'warn'}},
//trace: {label: 'trace', query: {expose: 'trace'}}
}
export {Middleware};
export {Reader};
export {Router};
export {Transform};
export {Writer};
export function resolvePipeline(scribe, pipeline) {
const resolved = [];
for (const through of pipeline) {
if (typeof through === 'function') {
resolved.push(new through(scribe));
} else if (typeof through === 'object') {
resolved.push(through);
} else if (typeof through === 'string') {
const Class = require(path.join(__dirname, through)).default;
resolved.push(new Class(scribe));
}
},
nwjs: {},
debug: false
};
export default function (id = process.pid, opts = rc('scribe', defaultOpts), ...exposers) {
opts = extend(true, {}, defaultOpts, opts);
var console = new BasicConsole(opts.name, id || opts.instanceId);
function appendTransforms(args) {
if (opts.mongo && opts.mongoUri && opts.socket && opts.socketPort) {
args.push(new JSON2Converter());
args.push(new MongoDB(opts.mongoUri, opts.debug));
args.push(new SocketIO(opts.socketPort, opts.debug));
} else if (opts.mongo && opts.mongoUri) {
args.push(new JSON2Converter());
args.push(new MongoDB(opts.mongoUri, opts.debug));
} else if (opts.socket && opts.socketPort) {
args.push(new JSON2Converter());
args.push(new SocketIO(opts.socketPort, opts.debug));
}
return args;
}
console.exposed().concat(exposers).forEach(expose => {
console.expose(expose);
return resolved;
}
let args = appendTransforms([expose, 'mongo-socket', new ErrorExtractor()]);
export function create(opts) {
opts = extend(true, rc('scribe', {}), opts);
console.pipe.apply(console, args);
// create default console
const console = new Reader.BasicConsole(opts);
const {expose: exposeMap, 'expose/pipeline': pipelineMap} = opts;
console.pipe(expose, 'bash',
new Inspector(opts.inspector),
new DefaultConsole());
});
[...console.exposed(), ...Object.keys(exposeMap)]
.forEach(expose => {
if (expose === 'default') return;
const pipelines = exposeMap[expose] || exposeMap.default;
if (Array.isArray(pipelines)) {
pipelines.forEach(pipeline => {
if (Array.isArray(pipelineMap[pipeline])) {
if (opts.debug) {
process.stdout.write(`Exposing ${expose} through ${pipeline}\n`);
}
let args = appendTransforms(['express', 'mongo-socket', new ErrorExtractor(), new ExpressExtractor()]);
console.expose(expose);
console.pipe(expose, pipeline, ...resolvePipeline(console, pipelineMap[pipeline]));
}
});
}
});
console.pipe.apply(console, args);
console.pipe('express', 'bash',
new ExpressExtractor(),
new ExpressInspector(),
new Inspector(opts.inspector),
new DefaultConsole());
console.viewer = create.bind(null, opts.mongo && opts.mongoUri, opts.web.router, opts.web.client, opts.debug);
console.build = ()=> {
// update
nativePackage.main = `${opts.publicUri}:${path.join(String(opts.web.client.port), opts.basePath)}`;
// save
fs.writeFileSync(`${__dirname}/../native/package.json`, JSON.stringify(nativePackage, null, 4), {encoding: 'utf8'});
const nw = new NwBuilder(extend(true, {
platforms: ['win', 'osx', 'linux'],
buildDir: `${__dirname}/../public/native`,
version: '0.12.3',
zip: true
}, opts.nwjs, {files: `${__dirname}/../native/**/**`}));
if (opts.debug) {
nw.on('log', d => console.log(d));
}
return nw.build();
};
process.on('uncaughtException', e => console.error(e).then(() => process.exit(1)));
if (opts.handleUncaughtException) {
process.on('uncaughtException', e => console.error(e).then(() => process.exit(1)));
}
return console;
};
}

View file

@ -0,0 +1,13 @@
export default class ErrorExtractor {
through(data, callback) {
data.args =
data.args.map(a =>
a instanceof Error ?
a.toJSON ?
a.toJSON() :
JSON.parse(JSON.stringify(a, ["message", "arguments", "type", "name", "stack", "code"]))
: a);
callback(null, data);
}
}

View file

@ -4,11 +4,11 @@ import auth from 'basic-auth'
export default class ExpressExtractor {
ip(req) {
return req.ip
|| req._remoteAddress
|| req.remoteAddress
|| (req.connection && req.connection.remoteAddress)
|| req.headers['x-forwarded-for']
|| undefined;
|| req._remoteAddress
|| req.remoteAddress
|| (req.connection && req.connection.remoteAddress)
|| req.headers['x-forwarded-for']
|| 'xx.x.x.xx';
}
header(res, field) {
@ -19,8 +19,8 @@ export default class ExpressExtractor {
var header = res.getHeader(field);
return Array.isArray(header)
? header.join(', ')
: header
? header.join(', ')
: header
}
remoteUser(req) {

View file

@ -38,7 +38,7 @@ export default class ExpressInspector {
const express = data.args[0];
data.transient['callsite'] = `${express.ip}`;
data.args = [`${chalk.bgGreen.black(` ${express.method} `)} ${chalk.gray(express.url)} ${this.status(express)} - ${express.contentLength} ${chalk.gray(`(${express.duration.toFixed(3)}ms)`)}`];
data.args = [`${chalk.bgGreen.black(` ${express.method} `)} ${chalk.gray(express.originalUrl)} ${this.status(express)} - ${express.contentLength} ${chalk.gray(`(${express.duration.toFixed(3)}ms)`)}`];
callback(null, data);
}

View file

@ -0,0 +1,8 @@
import * as JSON2 from '../libs/JSON2'
export default class FullTextSerialize {
through(data, callback) {
data.serialized = JSON2.stringify(data);
callback(null, data);
}
}

View file

@ -7,13 +7,12 @@ function inspect(x, ctx) {
}
export default class Inspector {
constructor(inspectOpts = {colors: true, showHidden: false,
depth: 5, pre: true, args: true, metrics: true, tags: true}) {
this.inspectOpts = inspectOpts;
constructor(scribe) {
this.options = scribe.module('transform/Inspector').options;
}
format(f, ...args) {
const ctx = this.inspectOpts;
const ctx = this.options;
if (typeof f !== 'string') {
var objects = [];
@ -55,7 +54,7 @@ export default class Inspector {
inspectTags(data) {
return ((data.persistent['tags'] || []).map(tag => chalk.cyan(`${String(tag).toUpperCase()}`)))
.concat((data.transient['tags'] || []).map(tag => chalk.magenta(`${String(tag).toUpperCase()}`))).join(' ');
.concat((data.transient['tags'] || []).map(tag => chalk.magenta(`${String(tag).toUpperCase()}`))).join(' ');
}
inspectPre(data) {
@ -78,11 +77,11 @@ export default class Inspector {
data.args = '';
}
const pre = this.inspectOpts.pre ? `${this.inspectPre(data)} ` : '';
const tags = this.inspectOpts.tags ? this.inspectTags(data) : '';
const metrics = this.inspectOpts.metrics ? this.inspectMetrics(data) : '';
const site = this.inspectOpts.callsite ? this.inspectCallSite(data) : '';
const pretty = this.inspectOpts.args ? this.inspectArguments(data) : '';
const pre = this.options.pre ? `${this.inspectPre(data)} ` : '';
const tags = this.options.tags ? this.inspectTags(data) : '';
const metrics = this.options.metrics ? this.inspectMetrics(data) : '';
const site = this.options.callsite ? this.inspectCallSite(data) : '';
const pretty = this.options.args ? this.inspectArguments(data) : '';
const inspected = pretty.split('\n').map(line =>`${pre} ${[tags, metrics, line, site].join(' ')}`);
data.inspected = inspected;

View file

@ -1,6 +1,6 @@
import * as JSON2 from '../libs/JSON2'
export default class JSON2Converter {
export default class ToJSON2 {
through(data, callback) {
callback(null, JSON.parse(JSON2.stringify(data)));
}

5
src/transform/index.js Normal file
View file

@ -0,0 +1,5 @@
export {default as ErrorExtractor} from './ErrorExtractor';
export {default as ExpressExtractor} from './ExpressExtractor';
export {default as ExpressInspector} from './ExpressInspector';
export {default as Inspector} from './Inspector';
export {default as ToJSON2} from './ToJSON2';

View file

@ -1,8 +0,0 @@
export default class ErrorExtractor {
through(data, callback) {
data.args = data.args.map(a => a instanceof Error ?
JSON.parse(JSON.stringify(a, ["message", "arguments", "type", "name", "stack"])) : a);
callback(null, data);
}
}

41
src/writer/MongoDB.js Normal file
View file

@ -0,0 +1,41 @@
import mongoose, {Schema} from 'mongoose';
import * as JSON2 from '../libs/JSON2';
import express, {Router} from 'express';
import jade from 'jade';
import basicAuth from 'basic-auth-connect';
import bodyParser from 'body-parser'
import JSONStream from 'JSONStream';
export const EntrySchema = new Schema({
transient: Schema.Types.Object,
persistent: Schema.Types.Object,
args: Schema.Types.Object,
date: {type: Date, default: Date.now},
expose: String,
serialized: String
});
EntrySchema.index({'persistent.app': 1, 'persistent.id': -1});
EntrySchema.index({'serialized': 1, 'comment': 'text'});
export default class MongoDB {
constructor(scribe) {
this.options = scribe.module('writer/MongoDB').options;
const conn = mongoose.createConnection(this.options.uri);
this._debug = this.options.debug;
this._entry = conn.model('Entry', EntrySchema);
}
get Entry() {
return this._entry;
}
through(data, callback) {
new this.Entry(data).save((err, data) => {
if (err && this._debug) console.error(err);
callback(err, data);
});
}
}

View file

@ -3,11 +3,11 @@ import io from 'socket.io'
let sio;
export default class SocketIO {
constructor(port = 4000, debug = false) {
process.env.DEBUG = debug && 'socket.io*';
constructor(scribe) {
this.options = scribe.module('writer/SocketIO').options;
if (!sio) {
sio = io(port);
sio = io(this.options.port, this.options.options);
}
}

3
src/writer/index.js Normal file
View file

@ -0,0 +1,3 @@
export {default as DefaultConsole} from './DefaultConsole';
export {default as MongoDB} from './MongoDB';
export {default as SocketIO} from './SocketIO'

View file

@ -1,24 +0,0 @@
import mongoose from 'mongoose'
import EntrySchema from '../schemas/entry'
import * as JSON2 from '../libs/JSON2'
export default class MongoDB {
constructor(address = 'mongodb://localhost/scribe', debug = false) {
mongoose.set('debug', debug);
const conn = mongoose.createConnection(address);
this._debug = debug;
this._entry = conn.model('Entry', EntrySchema);
}
get Entry() {
return this._entry;
}
through(data, callback) {
new this.Entry(data).save((err, data) => {
if (err && this._debug) console.error(err);
callback(err, data);
});
}
}

View file

@ -1,4 +1,4 @@
import Scribe from '../'
import * as Scribe from '../'
import express from 'express'
import expect from 'expect.js'
import request from 'superagent'
@ -8,7 +8,7 @@ describe('Basic Scribe', ()=> {
const port = 3008;
const requestsCount = 10;
const console = new Scribe();
const console = Scribe.create();
console.persistent('tags', ['mocha', 'scribe']);
@ -16,8 +16,9 @@ describe('Basic Scribe', ()=> {
const app = express();
let count = 0, did404 = 0;
const logger = new Scribe.Middleware.ExpressLogger(console);
app.use(console.middleware('express'));
app.use(logger.getMiddleware());
app.get('/test', (req, res) => {
if (did404) {
return res.json({test: new Array(parseInt(Math.random() * 50)).join('.')});
@ -35,16 +36,16 @@ describe('Basic Scribe', ()=> {
count++;
request
.get(`http://localhost:${port}/test`)
.end((err, req)=> {
if (err) {
return callback();
} else {
console
.log(`Received response ${count}/${requestsCount}`, req.body)
.then(()=>callback());
}
});
.get(`http://localhost:${port}/test`)
.end((err, req)=> {
if (err) {
return callback();
} else {
console
.log(`Received response ${count}/${requestsCount}`, req.body)
.then(()=>callback());
}
});
}, () => {
expect(count).to.equal(requestsCount);
done();

View file

@ -1,80 +1,65 @@
import Scribe from '../'
import * as Scribe from '../'
import expect from 'expect.js'
import TraceError from 'trace-error';
describe('Basic Scribe', ()=> {
const console = new Scribe();
const console = Scribe.create();
console.persistent('tags', ['mocha', 'scribe']);
it('should print objects to terminal', done => {
Promise.all([
console.tag('object').log('Inspect object', {test: true}),
console.tag('object').log('Inspect object', console),
console.log()
]).then(() => done());
it('should print objects to terminal', async () => {
await console.tag('object').log('Inspect object', {test: true});
await console.tag('object').log('Inspect object', console);
await console.log();
});
it('should print functions to terminal', done => {
Promise.all([
console.tag('function').log('Inspect function', Function)
]).then(() => done());
it('should print functions to terminal', async () => {
await console.tag('function').log('Inspect function', Function);
});
it('should print metrics to terminal', done => {
Promise.all([
console.tag('metric').metric('appTime', 500, 'dbTime', 750).log('Inspect metric'),
console.tag('metric').metric('appTime', 500, 'dbTime', 750).log()
]).then(() => done());
it('should print metrics to terminal', async () => {
await console.tag('metric').metric('appTime', 500, 'dbTime', 750).log('Inspect metric');
await console.tag('metric').metric('appTime', 500, 'dbTime', 750).log()
});
it('should print errors to terminal', done => {
Promise.all([
console.tag('error').error(new Error('Scribe error test 1')),
console.tag('error').error(new Error('Scribe error test 2'))
]).then(() => done());
it('should print errors to terminal', async () => {
await console.tag('error').error(new Error('Scribe error test 1'));
await console.tag('error').error(new Error('Scribe error test 2'));
await console.tag('error').error(new TraceError('Scribe error test 2', new Error("Sub error")));
await console.tag('error').error(JSON.stringify(new TraceError('test error', new Error("Sub error")),
["message", "arguments", "type", "name", "stack"]));
});
it('should print dates to terminal', done => {
Promise.all([
console.tag('date').log(new Date())
]).then(() => done());
it('should print dates to terminal', async () => {
await console.tag('date').log(new Date());
});
it('should print booleans to terminal', done => {
Promise.all([
console.tag('boolean').log(true)
]).then(() => done());
it('should print booleans to terminal', async () => {
await console.tag('boolean').log(true);
});
it('should print maps to terminal', done => {
it('should print maps to terminal', async () => {
let map0 = new Map(), map1 = new WeakMap();
map0.set('scribe', 'test');
map1.set({}, 'test');
Promise.all([
console.tag('map').log(map0),
console.tag('map').log(map1)
]).then(() => done());
await console.tag('map').log(map0);
await console.tag('map').log(map1);
});
it('should print sets to terminal', done => {
it('should print sets to terminal', async () => {
let set0 = new Set(), set1 = new WeakSet();
set0.add('scribe');
set1.add({});
Promise.all([
console.tag('set').log(set0),
console.tag('set').log(set1)
]).then(() => done());
await console.tag('set').log(set0);
await console.tag('set').log(set1);
});
it('should print promises to terminal', done => {
it('should print promises to terminal', async () => {
const promise = new Promise(()=> 0);
Promise.all([
console.tag('promise').log(promise, promise instanceof Promise)
]).then(() => done());
await console.tag('promise').log(promise, promise instanceof Promise);
});
});

View file

@ -1,6 +1,7 @@
doctype html
html
head
title Scribe.js
meta(charset="UTF-8")
link(rel='stylesheet', type='text/css', href='styles/viewer.min.css')
script.