big_query_log_viewer 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE.txt +23 -0
- data/README.md +49 -0
- data/Rakefile +19 -0
- data/app/assets/javascripts/big_query_log_viewer/application.coffee +40 -0
- data/app/assets/javascripts/big_query_log_viewer/components/pagination.coffee +35 -0
- data/app/assets/javascripts/big_query_log_viewer/components/row.coffee +55 -0
- data/app/assets/javascripts/big_query_log_viewer/components/search_box.coffee +20 -0
- data/app/assets/javascripts/big_query_log_viewer/components/search_status.coffee +18 -0
- data/app/assets/javascripts/big_query_log_viewer/components/tab.coffee +275 -0
- data/app/assets/javascripts/big_query_log_viewer/components/tab_manager.coffee +216 -0
- data/app/assets/javascripts/big_query_log_viewer/utils/query.coffee +61 -0
- data/app/assets/stylesheets/big_query_log_viewer/application.css.less +161 -0
- data/app/controllers/big_query_log_viewer/application_controller.rb +3 -0
- data/app/views/big_query_log_viewer/application/index.html.erb +29 -0
- data/config/coffeelint.json +10 -0
- data/config/routes.rb +3 -0
- data/lib/big_query_log_viewer.rb +5 -0
- data/lib/big_query_log_viewer/engine.rb +9 -0
- data/lib/big_query_log_viewer/version.rb +3 -0
- data/lib/generators/big_query_log_viewer/install_generator.rb +15 -0
- data/lib/generators/templates/big_query_log_viewer.rb +15 -0
- metadata +205 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 630d53dfd36e95d3a96822530d80f5cb00de1761
|
4
|
+
data.tar.gz: bebe698a4d02b570204b425b7d6667d31d19780e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 11a067c1f173f439bb56d35240cea08522b37a184b08af16b6bf07ce2f13afac14b33f5fb846704a9004897705ceaece23547c49d00d4fcc5e6bbf3db0a6f0de
|
7
|
+
data.tar.gz: bf5de5515ae6664f8c9f8770cf041309b8c9e9d736cebed026acd915b0bd2539c425ae470bcc04521012deef68e3df996782f67e95d1fc8f7fca2d14fc6622c8
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
|
2
|
+
Copyright (c) 2015 Aha! Labs Inc
|
3
|
+
|
4
|
+
MIT License
|
5
|
+
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
7
|
+
a copy of this software and associated documentation files (the
|
8
|
+
"Software"), to deal in the Software without restriction, including
|
9
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
10
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
11
|
+
permit persons to whom the Software is furnished to do so, subject to
|
12
|
+
the following conditions:
|
13
|
+
|
14
|
+
The above copyright notice and this permission notice shall be
|
15
|
+
included in all copies or substantial portions of the Software.
|
16
|
+
|
17
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
18
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
19
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
20
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
21
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
22
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
23
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
# BigQueryLogViewer
|
2
|
+
|
3
|
+
A simple Rails engine and React app to search logs stored in Google BigQuery.
|
4
|
+
|
5
|
+

|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'big_query_log_viewer'
|
13
|
+
```
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install big_query_log_viewer
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
Run the generator to create your initialization file:
|
26
|
+
|
27
|
+
$ rails generate big_query_log_viewer:install
|
28
|
+
|
29
|
+
Open the newly created file, `config/initializers/big_query_log_viewer.rb`, and add your client ID, project number, and table prefix from the [Google Developer Console](https://console.developers.google.com).
|
30
|
+
|
31
|
+
Finally, mount the engine by adding the following to your application's `routes.rb`:
|
32
|
+
|
33
|
+
`mount BigQueryLogViewer::Engine, at: '/some_url'`
|
34
|
+
|
35
|
+
## Linting
|
36
|
+
|
37
|
+
The project uses [coffeelint](http://www.coffeelint.org/) to maintain quality CoffeeScript syntax. It is configured to run as the default rake task.
|
38
|
+
|
39
|
+
## Authorship
|
40
|
+
|
41
|
+
Written by Zach Schneider, based on prototype by Chris Waters, for [Aha!, the world's #1 product roadmap software](http://www.aha.io/)
|
42
|
+
|
43
|
+
## Contributing
|
44
|
+
|
45
|
+
1. Fork it ( https://github.com/aha-app/bigquery-log-viewer/fork )
|
46
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
47
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
48
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
49
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/setup'
|
3
|
+
rescue LoadError
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
|
+
end
|
6
|
+
|
7
|
+
load 'rails/tasks/statistics.rake'
|
8
|
+
|
9
|
+
Bundler::GemHelper.install_tasks
|
10
|
+
|
11
|
+
require 'coffeelint'
|
12
|
+
|
13
|
+
task :coffeelint do
|
14
|
+
Coffeelint.lint_dir(File.join('app', 'assets', 'javascripts', 'big_query_log_viewer')) do |filename, lint_report|
|
15
|
+
Coffeelint.display_test_results(filename, lint_report)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
task default: :coffeelint
|
@@ -0,0 +1,40 @@
|
|
1
|
+
#= require react
|
2
|
+
#= require jquery
|
3
|
+
|
4
|
+
#= require ./components/tab_manager
|
5
|
+
|
6
|
+
window.BigQueryLogViewer ||= {}
|
7
|
+
|
8
|
+
TabManager = BigQueryLogViewer.TabManager
|
9
|
+
|
10
|
+
class BigQueryLogViewer.App
|
11
|
+
constructor: (@projectId, @clientId, @tablePrefix, @rowsPerPage) ->
|
12
|
+
config =
|
13
|
+
'client_id': @clientId,
|
14
|
+
'scope': 'https://www.googleapis.com/auth/bigquery'
|
15
|
+
immediate: true
|
16
|
+
|
17
|
+
# Perform authentication.
|
18
|
+
gapi.auth.authorize(config, (result) ->
|
19
|
+
if result.error
|
20
|
+
config.immediate = false
|
21
|
+
$('#authenticate-btn').show()
|
22
|
+
$('#authenticate-btn').on 'click', ->
|
23
|
+
gapi.auth.authorize(config, (result) ->
|
24
|
+
unless result.error
|
25
|
+
gapi.client.load('bigquery', 'v2')
|
26
|
+
$('#application').fadeIn()
|
27
|
+
$('#authenticate-btn').hide()
|
28
|
+
)
|
29
|
+
else
|
30
|
+
gapi.client.load('bigquery', 'v2')
|
31
|
+
$('#application').fadeIn()
|
32
|
+
$('#authenticate-btn').hide()
|
33
|
+
)
|
34
|
+
|
35
|
+
# Create React element.
|
36
|
+
props =
|
37
|
+
projectId: @projectId
|
38
|
+
tablePrefix: @tablePrefix
|
39
|
+
rowsPerPage: @rowsPerPage
|
40
|
+
React.render(React.createElement(TabManager, props), $('#application')[0])
|
@@ -0,0 +1,35 @@
|
|
1
|
+
###* @jsx React.DOM ###
|
2
|
+
|
3
|
+
window.BigQueryLogViewer ||= {}
|
4
|
+
|
5
|
+
BigQueryLogViewer.Pagination = React.createClass
|
6
|
+
render: ->
|
7
|
+
removeNode =
|
8
|
+
if @props.type == 'top'
|
9
|
+
<span className={'remove-wrapper'} onClick={@props.handleTabDelete}>
|
10
|
+
<i className={'icon icon-remove'}></i>
|
11
|
+
</span>
|
12
|
+
|
13
|
+
tabs =
|
14
|
+
for tab in @props.tabs
|
15
|
+
<li key={tab.key} onClick={tab.handler || @props.handleTabSwitch} className={'active' if tab.active}>
|
16
|
+
<a href={'#'}>
|
17
|
+
<span dangerouslySetInnerHTML={__html: tab.title} />
|
18
|
+
{removeNode}
|
19
|
+
</a>
|
20
|
+
</li>
|
21
|
+
|
22
|
+
if @props.type == 'top'
|
23
|
+
return (
|
24
|
+
<ul className={'nav nav-tabs'}>
|
25
|
+
{tabs}
|
26
|
+
</ul>
|
27
|
+
)
|
28
|
+
else
|
29
|
+
return (
|
30
|
+
<div className={'pagination'}>
|
31
|
+
<ul>
|
32
|
+
{tabs}
|
33
|
+
</ul>
|
34
|
+
</div>
|
35
|
+
)
|
@@ -0,0 +1,55 @@
|
|
1
|
+
###* @jsx React.DOM ###
|
2
|
+
|
3
|
+
window.BigQueryLogViewer ||= {}
|
4
|
+
|
5
|
+
BigQueryLogViewer.Row = React.createClass
|
6
|
+
getInitialState: ->
|
7
|
+
s = "000#{@props.row.timestamp.getMilliseconds()}"
|
8
|
+
y = @props.row.timestamp.getFullYear()
|
9
|
+
mo = ('0' + @props.row.timestamp.getMonth()).slice(-2)
|
10
|
+
d = ('0' + @props.row.timestamp.getDate()).slice(-2)
|
11
|
+
h = ('0' + @props.row.timestamp.getHours()).slice(-2)
|
12
|
+
mi = ('0' + @props.row.timestamp.getMinutes()).slice(-2)
|
13
|
+
se = ('0' + @props.row.timestamp.getSeconds()).slice(-2)
|
14
|
+
{
|
15
|
+
tss: "#{y}-#{mo}-#{d} #{h}:#{mi}:#{se}"
|
16
|
+
tsm: s.substr(s.length - 3)
|
17
|
+
}
|
18
|
+
handleShowMore: (e) ->
|
19
|
+
e.preventDefault()
|
20
|
+
$(@getDOMNode()).find('.collapsed-string').show()
|
21
|
+
$(@getDOMNode()).find('.expand-row').hide()
|
22
|
+
|
23
|
+
handleShowProximity: (e) ->
|
24
|
+
e.preventDefault()
|
25
|
+
@props.handleShowProximity(@props.row)
|
26
|
+
|
27
|
+
render: ->
|
28
|
+
row = @props.row
|
29
|
+
|
30
|
+
collapsedMsg =
|
31
|
+
if row.msg.length > 200
|
32
|
+
<span>{row.msg.substring(0,200)}...<a href='#' className='expand-row' onClick={@handleShowMore}>expand</a><span className='collapsed-string'>{row.msg.substring(200)}</span></span>
|
33
|
+
else
|
34
|
+
row.msg
|
35
|
+
|
36
|
+
if @props.type == 'results'
|
37
|
+
return (
|
38
|
+
<tr>
|
39
|
+
<td className='column-controls' onClick={@handleShowProximity}><i className='icon icon-external-link'></i></td>
|
40
|
+
<td className='column-ts'>{@state.tss}<span className='ts-milliseconds'>.{@state.tsm}</span></td>
|
41
|
+
<td className='column-host'>{row.host}</td>
|
42
|
+
<td className='column-pid'>{row.pid}</td>
|
43
|
+
<td className='column-severity' data-severity={row.severity}>{row.severity}</td>
|
44
|
+
<td className='column-msg'><pre>{collapsedMsg}</pre></td>
|
45
|
+
</tr>
|
46
|
+
)
|
47
|
+
else
|
48
|
+
return (
|
49
|
+
<tr className={'highlight-row' if @props.highlighted}>
|
50
|
+
<td className='column-ts'>{@state.tss}<span className='ts-milliseconds'>.{@state.tsm}</span></td>
|
51
|
+
<td className='column-rid'>{row.rid}</td>
|
52
|
+
<td className='column-severity' data-severity={row.severity}>{row.severity}</td>
|
53
|
+
<td className='column-msg'><pre>{collapsedMsg}</pre></td>
|
54
|
+
</tr>
|
55
|
+
)
|
@@ -0,0 +1,20 @@
|
|
1
|
+
###* @jsx React.DOM ###
|
2
|
+
|
3
|
+
window.BigQueryLogViewer ||= {}
|
4
|
+
|
5
|
+
BigQueryLogViewer.SearchBox = React.createClass
|
6
|
+
handleSearch: (e) ->
|
7
|
+
e.preventDefault()
|
8
|
+
@props.handleSearch(@refs.searchInput.getDOMNode().value, @refs.startDate.getDOMNode().value, @refs.endDate.getDOMNode().value)
|
9
|
+
|
10
|
+
render: ->
|
11
|
+
return (
|
12
|
+
<form className='navbar-form search-box form-inline' onSubmit={@handleSearch}>
|
13
|
+
<div className='controls'>
|
14
|
+
<input ref='searchInput' placeholder={'Term'} />
|
15
|
+
Start Date: <input ref='startDate' type='date' />
|
16
|
+
End Date: <input ref='endDate' type='date' />
|
17
|
+
<input type='submit' value='Search' className='btn btn-primary' />
|
18
|
+
</div>
|
19
|
+
</form>
|
20
|
+
)
|
@@ -0,0 +1,18 @@
|
|
1
|
+
###* @jsx React.DOM ###
|
2
|
+
|
3
|
+
window.BigQueryLogViewer ||= {}
|
4
|
+
|
5
|
+
BigQueryLogViewer.SearchStatus = React.createClass
|
6
|
+
render: ->
|
7
|
+
if @props.queryInProgress
|
8
|
+
progress = <span>Query running...</span>
|
9
|
+
else if @props.errorMessage
|
10
|
+
progress = <span className='error'>{@props.errorMessage}</span>
|
11
|
+
else if !@props.queryInProgress and @props.numReturnedResults?
|
12
|
+
results = <span>Found {@props.numReturnedResults} results</span>
|
13
|
+
return (
|
14
|
+
<div className='query-status'>
|
15
|
+
{progress}
|
16
|
+
{results}
|
17
|
+
</div>
|
18
|
+
)
|
@@ -0,0 +1,275 @@
|
|
1
|
+
###* @jsx React.DOM ###
|
2
|
+
|
3
|
+
#= require ./pagination
|
4
|
+
#= require ./row
|
5
|
+
|
6
|
+
window.BigQueryLogViewer ||= {}
|
7
|
+
|
8
|
+
Pagination = BigQueryLogViewer.Pagination
|
9
|
+
Row = BigQueryLogViewer.Row
|
10
|
+
|
11
|
+
BigQueryLogViewer.Tab = React.createClass
|
12
|
+
getInitialState: ->
|
13
|
+
{
|
14
|
+
pages: [@props.tab.rowData]
|
15
|
+
activePageIndex: 0
|
16
|
+
pageToken: @props.tab.pageToken
|
17
|
+
showPrevLink: true
|
18
|
+
showNextLink: true
|
19
|
+
currentNextPage: 0
|
20
|
+
currentPrevPage: 0
|
21
|
+
}
|
22
|
+
|
23
|
+
resultsTab: ->
|
24
|
+
@props.tab.type is 'results'
|
25
|
+
|
26
|
+
expansionTab: ->
|
27
|
+
@props.tab.type is 'expansion'
|
28
|
+
|
29
|
+
highlighted: (row) ->
|
30
|
+
@expansionTab() && @props.tab.row.rid is row.rid
|
31
|
+
|
32
|
+
prevTitle: ->
|
33
|
+
if @resultsTab() then 'Prev' else 'More'
|
34
|
+
|
35
|
+
nextTitle: ->
|
36
|
+
if @resultsTab() then 'Next' else 'More'
|
37
|
+
|
38
|
+
handleNextPage: ->
|
39
|
+
if @state.activePageIndex + 1 < @state.pages.length
|
40
|
+
# Page has already been loaded, just activate it
|
41
|
+
@setState(activePageIndex: @state.activePageIndex + 1)
|
42
|
+
else if @resultsTab()
|
43
|
+
# Else load next page from Google.
|
44
|
+
@props.query.executeListQuery({pageToken: @state.pageToken, jobId: @props.tab.jobId}, (response) =>
|
45
|
+
rows =
|
46
|
+
for r in response.rows
|
47
|
+
{
|
48
|
+
timestamp: new Date(1000 * r.f[0].v)
|
49
|
+
host: r.f[4].v
|
50
|
+
pid: r.f[3].v
|
51
|
+
rid: parseInt(r.f[1].v)
|
52
|
+
severity: r.f[2].v
|
53
|
+
msg: r.f[5].v
|
54
|
+
}
|
55
|
+
@state.pages.push(rows)
|
56
|
+
@setState(
|
57
|
+
pageToken: response.pageToken
|
58
|
+
activePageIndex: @state.activePageIndex + 1
|
59
|
+
)
|
60
|
+
|
61
|
+
, (response) =>
|
62
|
+
console.log "ERROR: #{response.message}; entire response follows"
|
63
|
+
console.log response
|
64
|
+
alert 'Error loading more rows; check console for more information'
|
65
|
+
)
|
66
|
+
else if @expansionTab()
|
67
|
+
# Load additional context.
|
68
|
+
startRow = @props.tab.row.rid + @props.rowsPerPage / 2 + @state.currentNextPage * @props.rowsPerPage
|
69
|
+
endRow = @props.tab.row.rid + @props.rowsPerPage / 2 + (@state.currentNextPage + 1) * @props.rowsPerPage
|
70
|
+
|
71
|
+
# Construct query.
|
72
|
+
conds = [
|
73
|
+
{
|
74
|
+
field: 'host'
|
75
|
+
method: 'equals'
|
76
|
+
type: 'string'
|
77
|
+
value: @props.tab.row.host
|
78
|
+
}
|
79
|
+
{
|
80
|
+
field: 'pid'
|
81
|
+
method: 'equals'
|
82
|
+
type: 'int'
|
83
|
+
value: @props.tab.row.pid
|
84
|
+
}
|
85
|
+
{
|
86
|
+
field: 'rid'
|
87
|
+
method: 'between'
|
88
|
+
firstValue: startRow
|
89
|
+
secondValue: endRow
|
90
|
+
}
|
91
|
+
]
|
92
|
+
query = @props.query.buildQuery(@props.tab.source.startDate, @props.tab.source.endDate, conds, 'ts, rid desc')
|
93
|
+
|
94
|
+
@props.query.executeQuery(query, {maxResults: @props.rowsPerPage}, (response) =>
|
95
|
+
# Mark no more next if there were no results.
|
96
|
+
if parseInt(response.totalRows) == 0
|
97
|
+
@setState(showNextLink: false)
|
98
|
+
return
|
99
|
+
|
100
|
+
# Create new page for the expansion.
|
101
|
+
rows =
|
102
|
+
for r in response.rows
|
103
|
+
{
|
104
|
+
timestamp: new Date(1000 * r.f[0].v)
|
105
|
+
host: r.f[4].v
|
106
|
+
pid: r.f[3].v
|
107
|
+
rid: parseInt(r.f[1].v)
|
108
|
+
severity: r.f[2].v
|
109
|
+
msg: r.f[5].v
|
110
|
+
}
|
111
|
+
|
112
|
+
@state.pages[0] = @state.pages[0].concat(rows)
|
113
|
+
@setState(currentNextPage: @state.currentNextPage + 1)
|
114
|
+
|
115
|
+
, (reponse) =>
|
116
|
+
console.log "ERROR: #{response.message}; entire response follows"
|
117
|
+
console.log response
|
118
|
+
alert 'Error finding more context; check console for more information'
|
119
|
+
)
|
120
|
+
|
121
|
+
handlePrevPage: ->
|
122
|
+
if @resultsTab()
|
123
|
+
@setState(activePageIndex: @state.activePageIndex - 1)
|
124
|
+
else if @expansionTab()
|
125
|
+
# Load additional context.
|
126
|
+
startRow = @props.tab.row.rid - @props.rowsPerPage / 2 - @state.currentPrevPage * @props.rowsPerPage
|
127
|
+
endRow = @props.tab.row.rid - @props.rowsPerPage / 2 - (@state.currentPrevPage + 1) * @props.rowsPerPage
|
128
|
+
|
129
|
+
# Construct query.
|
130
|
+
conds = [
|
131
|
+
{
|
132
|
+
field: 'host'
|
133
|
+
method: 'equals'
|
134
|
+
type: 'string'
|
135
|
+
value: @props.tab.row.host
|
136
|
+
}
|
137
|
+
{
|
138
|
+
field: 'pid'
|
139
|
+
method: 'equals'
|
140
|
+
type: 'int'
|
141
|
+
value: @props.tab.row.pid
|
142
|
+
}
|
143
|
+
{
|
144
|
+
field: 'rid'
|
145
|
+
method: 'between'
|
146
|
+
firstValue: startRow
|
147
|
+
secondValue: endRow
|
148
|
+
}
|
149
|
+
]
|
150
|
+
query = @props.query.buildQuery(@props.tab.source.startDate, @props.tab.source.endDate, conds, 'ts, rid desc')
|
151
|
+
|
152
|
+
@props.query.executeQuery(query, {maxResults: @props.rowsPerPage}, (response) =>
|
153
|
+
# Mark no more prev if there were no results.
|
154
|
+
if parseInt(response.totalRows) == 0
|
155
|
+
@setState(showPrevLink: false)
|
156
|
+
return
|
157
|
+
|
158
|
+
# Create new page for the expansion.
|
159
|
+
rows =
|
160
|
+
for r in response.rows
|
161
|
+
{
|
162
|
+
timestamp: new Date(1000 * r.f[0].v)
|
163
|
+
host: r.f[4].v
|
164
|
+
pid: r.f[3].v
|
165
|
+
rid: parseInt(r.f[1].v)
|
166
|
+
severity: r.f[2].v
|
167
|
+
msg: r.f[5].v
|
168
|
+
}
|
169
|
+
|
170
|
+
@state.pages[0] = rows.concat(@state.pages[0])
|
171
|
+
@setState(currentPrevPage: @state.currentPrevPage + 1)
|
172
|
+
|
173
|
+
, (reponse) =>
|
174
|
+
console.log "ERROR: #{response.message}; entire response follows"
|
175
|
+
console.log response
|
176
|
+
alert 'Error finding more context; check console for more information'
|
177
|
+
)
|
178
|
+
|
179
|
+
handleShowPage: (event) ->
|
180
|
+
@setState(activePageIndex: parseInt(event.dispatchMarker.split('pagination-link-')[1]))
|
181
|
+
|
182
|
+
componentDidMount: ->
|
183
|
+
if @expansionTab()
|
184
|
+
window.requestAnimationFrame =>
|
185
|
+
node = @getDOMNode()
|
186
|
+
node.scrollTop = $(node).find('.highlight-row').offset().top
|
187
|
+
|
188
|
+
componentDidUpdate: ->
|
189
|
+
node = $(@getDOMNode())
|
190
|
+
maxHeight = $(window).height() - node.offset().top - 40
|
191
|
+
node.css('max-height', maxHeight)
|
192
|
+
|
193
|
+
render: ->
|
194
|
+
showProximity = @props.showProximity
|
195
|
+
tab = @props.tab
|
196
|
+
|
197
|
+
rows =
|
198
|
+
for row in @state.pages[@state.activePageIndex]
|
199
|
+
<Row key={"#{row.pid}-#{row.rid}"} row={row} type={@props.tab.type} highlighted={@highlighted(row)} handleShowProximity={@props.handleShowProximity} />
|
200
|
+
|
201
|
+
# Generate pagination.
|
202
|
+
if @resultsTab()
|
203
|
+
pagination = []
|
204
|
+
|
205
|
+
if @state.activePageIndex > 0 || (@expansionTab() && @state.showPrevLink)
|
206
|
+
pagination.push(
|
207
|
+
title: @prevTitle()
|
208
|
+
active: false
|
209
|
+
key: 'pagination-link-prev'
|
210
|
+
handler: @handlePrevPage
|
211
|
+
)
|
212
|
+
|
213
|
+
for page, index in @state.pages
|
214
|
+
pagination.push(
|
215
|
+
title: (index + 1)
|
216
|
+
active: index == @state.activePageIndex
|
217
|
+
key: "pagination-link-#{index}"
|
218
|
+
handler: @handleShowPage
|
219
|
+
)
|
220
|
+
|
221
|
+
if @state.activePageIndex + 1 < @state.pages.length || (@resultsTab() && @state.pageToken) || (@expansionTab() && @state.showNextLink)
|
222
|
+
pagination.push(
|
223
|
+
title: @nextTitle()
|
224
|
+
active: false
|
225
|
+
key: 'pagination-link-next'
|
226
|
+
handler: @handleNextPage
|
227
|
+
)
|
228
|
+
|
229
|
+
head =
|
230
|
+
if @resultsTab()
|
231
|
+
<thead>
|
232
|
+
<tr>
|
233
|
+
<th></th>
|
234
|
+
<th>Timestamp</th>
|
235
|
+
<th>Host</th>
|
236
|
+
<th>PID</th>
|
237
|
+
<th>Severity</th>
|
238
|
+
<th>Message</th>
|
239
|
+
</tr>
|
240
|
+
</thead>
|
241
|
+
else
|
242
|
+
<thead>
|
243
|
+
<tr>
|
244
|
+
<th>Timestamp</th>
|
245
|
+
<th>RID</th>
|
246
|
+
<th>Severity</th>
|
247
|
+
<th>Message</th>
|
248
|
+
</tr>
|
249
|
+
</thead>
|
250
|
+
|
251
|
+
pagination =
|
252
|
+
if @resultsTab()
|
253
|
+
<Pagination type={'inside'} tabs={pagination} handleTabSwitch={@handleTabSwitch} />
|
254
|
+
|
255
|
+
showMoreTop =
|
256
|
+
if @expansionTab() && @state.showPrevLink
|
257
|
+
<a href={'#'} onClick={@handlePrevPage}>More</a>
|
258
|
+
|
259
|
+
showMoreBottom =
|
260
|
+
if @expansionTab() && @state.showNextLink
|
261
|
+
<a href={'#'} onClick={@handleNextPage}>More</a>
|
262
|
+
|
263
|
+
return (
|
264
|
+
<div className={'hidden' unless @props.visible}>
|
265
|
+
{showMoreTop}
|
266
|
+
<table className={'table row-viewer'}>
|
267
|
+
{head}
|
268
|
+
<tbody>
|
269
|
+
{rows}
|
270
|
+
</tbody>
|
271
|
+
</table>
|
272
|
+
{pagination}
|
273
|
+
{showMoreBottom}
|
274
|
+
</div>
|
275
|
+
)
|
@@ -0,0 +1,216 @@
|
|
1
|
+
###* @jsx React.DOM ###
|
2
|
+
|
3
|
+
#= require ../utils/query
|
4
|
+
|
5
|
+
#= require ./tab
|
6
|
+
#= require ./search_box
|
7
|
+
#= require ./search_status
|
8
|
+
#= require ./pagination
|
9
|
+
|
10
|
+
window.BigQueryLogViewer ||= {}
|
11
|
+
|
12
|
+
Tab = BigQueryLogViewer.Tab
|
13
|
+
SearchBox = BigQueryLogViewer.SearchBox
|
14
|
+
SearchStatus = BigQueryLogViewer.SearchStatus
|
15
|
+
Pagination = BigQueryLogViewer.Pagination
|
16
|
+
|
17
|
+
Query = BigQueryLogViewer.Query
|
18
|
+
|
19
|
+
BigQueryLogViewer.TabManager = React.createClass
|
20
|
+
getInitialState: ->
|
21
|
+
@query = new BigQueryLogViewer.Query(@props.projectId, @props.tablePrefix, @props.rowsPerPage)
|
22
|
+
|
23
|
+
{
|
24
|
+
tabs: []
|
25
|
+
activeTabIndex: null
|
26
|
+
queryInProgress: false
|
27
|
+
numReturnedResults: null
|
28
|
+
errorMessage: null
|
29
|
+
}
|
30
|
+
|
31
|
+
activeTab: ->
|
32
|
+
@state.tabs[@state.activeTabIndex]
|
33
|
+
|
34
|
+
findResultsTab: (term, startDate, endDate) ->
|
35
|
+
(index for tab, index in @state.tabs when tab.type == 'results' && tab.term is term && tab.startDate is startDate && tab.endDate == endDate)[0]
|
36
|
+
|
37
|
+
findExpansionTab: (rid) ->
|
38
|
+
(index for tab, index in @state.tabs when tab.type == 'expansion' && tab.source == @activeTab() && tab.rid is rid)[0]
|
39
|
+
|
40
|
+
handleSearch: (searchTerm, startDate, endDate) ->
|
41
|
+
return if searchTerm == ''
|
42
|
+
startDate = null if startDate == ''
|
43
|
+
endDate = null if endDate == ''
|
44
|
+
|
45
|
+
# Check that valid dates are entered.
|
46
|
+
if (startDate == null) && (endDate != null) || (startDate != null) && (endDate == null)
|
47
|
+
alert 'Must fill in both or neither of start and end dates'
|
48
|
+
return
|
49
|
+
|
50
|
+
# Check to see if we've already searched this term.
|
51
|
+
if (foundTab = @findResultsTab(searchTerm, startDate, endDate)) != undefined
|
52
|
+
@setState(activeTabIndex: foundTab)
|
53
|
+
return
|
54
|
+
|
55
|
+
@setState(queryInProgress: true)
|
56
|
+
|
57
|
+
# Construct query.
|
58
|
+
conds = [
|
59
|
+
{
|
60
|
+
field: 'msg'
|
61
|
+
method: 'contains'
|
62
|
+
value: searchTerm
|
63
|
+
}
|
64
|
+
]
|
65
|
+
query = @query.buildQuery(startDate, endDate, conds, 'ts desc')
|
66
|
+
|
67
|
+
@query.executeQuery(query, {}, (response) =>
|
68
|
+
# Create new tab for the results.
|
69
|
+
unless parseInt(response.totalRows) == 0
|
70
|
+
rows =
|
71
|
+
for r in response.rows
|
72
|
+
{
|
73
|
+
timestamp: new Date(1000 * r.f[0].v)
|
74
|
+
host: r.f[4].v
|
75
|
+
pid: r.f[3].v
|
76
|
+
rid: parseInt(r.f[1].v)
|
77
|
+
severity: r.f[2].v
|
78
|
+
msg: r.f[5].v
|
79
|
+
}
|
80
|
+
|
81
|
+
tab =
|
82
|
+
type: 'results'
|
83
|
+
rowData: rows
|
84
|
+
pageToken: response.pageToken
|
85
|
+
jobId: response.jobReference.jobId
|
86
|
+
term: searchTerm
|
87
|
+
startDate: startDate
|
88
|
+
endDate: endDate
|
89
|
+
|
90
|
+
position = if @state.activeTabIndex != null then @state.activeTabIndex + 1 else 0
|
91
|
+
@state.tabs.splice(position, 0, tab)
|
92
|
+
@setState(activeTabIndex: position)
|
93
|
+
|
94
|
+
# Update view component.
|
95
|
+
@setState
|
96
|
+
queryInProgress: false
|
97
|
+
numReturnedResults: response.totalRows
|
98
|
+
errorMessage: null
|
99
|
+
, (response) =>
|
100
|
+
@setState
|
101
|
+
queryInProgress: false
|
102
|
+
numReturnedResults: 0
|
103
|
+
errorMessage: response.message
|
104
|
+
)
|
105
|
+
|
106
|
+
handleShowProximity: (row) ->
|
107
|
+
# Check to see if we've already queried this row proximity.
|
108
|
+
if (foundTab = @findExpansionTab(row.rid)) != undefined
|
109
|
+
@setState(activeTabIndex: foundTab)
|
110
|
+
return
|
111
|
+
|
112
|
+
@setState(queryInProgress: true)
|
113
|
+
|
114
|
+
# Construct query.
|
115
|
+
conds = [
|
116
|
+
{
|
117
|
+
field: 'host'
|
118
|
+
method: 'equals'
|
119
|
+
type: 'string'
|
120
|
+
value: row.host
|
121
|
+
}
|
122
|
+
{
|
123
|
+
field: 'pid'
|
124
|
+
method: 'equals'
|
125
|
+
type: 'int'
|
126
|
+
value: row.pid
|
127
|
+
}
|
128
|
+
{
|
129
|
+
field: 'rid'
|
130
|
+
method: 'between'
|
131
|
+
firstValue: row.rid - @props.rowsPerPage / 2
|
132
|
+
secondValue: row.rid + @props.rowsPerPage / 2
|
133
|
+
}
|
134
|
+
]
|
135
|
+
query = @query.buildQuery(@activeTab().startDate, @activeTab().endDate, conds, 'ts, rid desc')
|
136
|
+
|
137
|
+
@query.executeQuery(query, {maxResults: 101}, (response) =>
|
138
|
+
# Create new tab for the expansion.
|
139
|
+
rows =
|
140
|
+
for r in response.rows
|
141
|
+
{
|
142
|
+
timestamp: new Date(1000 * r.f[0].v)
|
143
|
+
host: r.f[4].v
|
144
|
+
pid: r.f[3].v
|
145
|
+
rid: parseInt(r.f[1].v)
|
146
|
+
severity: r.f[2].v
|
147
|
+
msg: r.f[5].v
|
148
|
+
}
|
149
|
+
tab =
|
150
|
+
type: 'expansion'
|
151
|
+
rowData: rows
|
152
|
+
row: row
|
153
|
+
source: @activeTab()
|
154
|
+
term: @activeTab().term
|
155
|
+
position = if @state.activeTabIndex != null then @state.activeTabIndex + 1 else 0
|
156
|
+
@state.tabs.splice(position, 0, tab)
|
157
|
+
@setState(activeTabIndex: position)
|
158
|
+
|
159
|
+
# Update the view component.
|
160
|
+
@setState
|
161
|
+
queryInProgress: false
|
162
|
+
numReturnedResults: response.totalRows
|
163
|
+
errorMessage: null
|
164
|
+
, (reponse) =>
|
165
|
+
console.log "ERROR: #{response.message}; entire response follows"
|
166
|
+
console.log response
|
167
|
+
alert 'Error finding nearby rows; check console for more information'
|
168
|
+
)
|
169
|
+
|
170
|
+
handleTabSwitch: (event) ->
|
171
|
+
@setState(activeTabIndex: parseInt(event.dispatchMarker.split('tab-name-')[1]))
|
172
|
+
|
173
|
+
handleTabDelete: (event) ->
|
174
|
+
index = parseInt(event.dispatchMarker.split('tab-name-')[1])
|
175
|
+
|
176
|
+
newIndex =
|
177
|
+
if index <= @state.activeTabIndex
|
178
|
+
if @state.activeTabIndex > 0
|
179
|
+
@state.activeTabIndex - 1
|
180
|
+
else
|
181
|
+
null
|
182
|
+
else
|
183
|
+
@state.activeTabIndex
|
184
|
+
|
185
|
+
@state.tabs.splice(index, 1)
|
186
|
+
@setState(activeTabIndex: newIndex)
|
187
|
+
|
188
|
+
render: ->
|
189
|
+
# Create tab list for pagination.
|
190
|
+
pagination =
|
191
|
+
for tab, index in @state.tabs
|
192
|
+
title = tab.term
|
193
|
+
title = "<i class='icon icon-external-link'></i> #{title}" if tab.type == 'expansion'
|
194
|
+
|
195
|
+
{
|
196
|
+
title: title
|
197
|
+
active: index == @state.activeTabIndex
|
198
|
+
key: "tab-name-#{index}"
|
199
|
+
}
|
200
|
+
|
201
|
+
tabs =
|
202
|
+
for tab, index in @state.tabs
|
203
|
+
<Tab key={"tab-#{index}"} tab={tab} query={@query} visible={index == @state.activeTabIndex} handleShowProximity={@handleShowProximity} rowsPerPage={@props.rowsPerPage} />
|
204
|
+
|
205
|
+
return (
|
206
|
+
<div>
|
207
|
+
<div>
|
208
|
+
<SearchBox handleSearch={@handleSearch} />
|
209
|
+
<SearchStatus queryInProgress={@state.queryInProgress} numReturnedResults={@state.numReturnedResults} errorMessage={@state.errorMessage} />
|
210
|
+
<Pagination type={'top'} tabs={pagination} handleTabSwitch={@handleTabSwitch} handleTabDelete={@handleTabDelete} />
|
211
|
+
</div>
|
212
|
+
<div className={'tabs'}>
|
213
|
+
{tabs}
|
214
|
+
</div>
|
215
|
+
</div>
|
216
|
+
)
|
@@ -0,0 +1,61 @@
|
|
1
|
+
window.BigQueryLogViewer ||= {}
|
2
|
+
|
3
|
+
class BigQueryLogViewer.Query
|
4
|
+
constructor: (@projectId, @tablePrefix, @rowsPerPage) ->
|
5
|
+
#
|
6
|
+
|
7
|
+
tableRange: (startDate='CURRENT_TIMESTAMP()', endDate='CURRENT_TIMESTAMP()') ->
|
8
|
+
startDate = "TIMESTAMP('#{startDate} 00:00:00')" unless startDate == "CURRENT_TIMESTAMP()"
|
9
|
+
endDate = "TIMESTAMP('#{endDate} 23:59:59')" unless endDate == "CURRENT_TIMESTAMP()"
|
10
|
+
|
11
|
+
"(SELECT * FROM TABLE_DATE_RANGE(logs.#{@tablePrefix}_, #{startDate}, #{endDate}))"
|
12
|
+
|
13
|
+
buildQuery: (startDate, endDate, conds, order) ->
|
14
|
+
# Construct conditions.
|
15
|
+
conds =
|
16
|
+
for cond in conds
|
17
|
+
value = cond.value.replace(/[\\"']/g, '\\$&').replace(/\u0000/g, '\\0') if cond.value
|
18
|
+
switch cond.method
|
19
|
+
when 'contains' then "#{cond.field} contains '#{value}'"
|
20
|
+
when 'equals'
|
21
|
+
if cond.type == 'string'
|
22
|
+
"#{cond.field} = '#{value}'"
|
23
|
+
else
|
24
|
+
"#{cond.field} = #{value}"
|
25
|
+
when 'between' then "#{cond.field} between #{cond.firstValue} and #{cond.secondValue}"
|
26
|
+
|
27
|
+
q = "SELECT ts, rid, sev, pid, host, msg FROM #{@tableRange(startDate, endDate)}"
|
28
|
+
q = "#{q} WHERE #{conds.join(' AND ')}" if conds.length > 0
|
29
|
+
q = "#{q} ORDER BY #{order}" if order
|
30
|
+
q
|
31
|
+
|
32
|
+
executeQuery: (query, config, success, error) ->
|
33
|
+
console.log("Executing query: #{query}")
|
34
|
+
config.maxResults ?= @rowsPerPage
|
35
|
+
request = gapi.client.bigquery.jobs.query
|
36
|
+
projectId: @projectId
|
37
|
+
timeoutMs: 30000
|
38
|
+
maxResults: config.maxResults
|
39
|
+
query: query
|
40
|
+
|
41
|
+
request.execute (response) ->
|
42
|
+
if response.error
|
43
|
+
error(response) if error?
|
44
|
+
else
|
45
|
+
success(response) if success?
|
46
|
+
|
47
|
+
executeListQuery: (config, success, error) ->
|
48
|
+
console.log('Executing list query')
|
49
|
+
config.maxResults ?= @rowsPerPage
|
50
|
+
request = gapi.client.bigquery.jobs.getQueryResults
|
51
|
+
projectId: @projectId
|
52
|
+
jobId: config.jobId
|
53
|
+
timeoutMs: 30000
|
54
|
+
maxResults: config.maxResults
|
55
|
+
pageToken: config.pageToken
|
56
|
+
|
57
|
+
request.execute (response) ->
|
58
|
+
if response.error
|
59
|
+
error(response) if error?
|
60
|
+
else
|
61
|
+
success(response) if success?
|
@@ -0,0 +1,161 @@
|
|
1
|
+
/*
|
2
|
+
*= require twitter/bootstrap
|
3
|
+
*= require font-awesome
|
4
|
+
*/
|
5
|
+
|
6
|
+
body {
|
7
|
+
font-size: 13px;
|
8
|
+
font-family: Helvetica, Arial, sans-serif;
|
9
|
+
padding: 25px;
|
10
|
+
padding-top: 5px;
|
11
|
+
margin: 0px;
|
12
|
+
}
|
13
|
+
|
14
|
+
.wrapper {
|
15
|
+
margin-left: auto;
|
16
|
+
margin-right: auto;
|
17
|
+
}
|
18
|
+
|
19
|
+
a {
|
20
|
+
color: #ccc;
|
21
|
+
}
|
22
|
+
|
23
|
+
.hidden {
|
24
|
+
display: none;
|
25
|
+
}
|
26
|
+
|
27
|
+
#authenticate-btn, #application {
|
28
|
+
display: none;
|
29
|
+
}
|
30
|
+
|
31
|
+
.controls {
|
32
|
+
input {
|
33
|
+
margin-right: 5px;
|
34
|
+
padding: 3px;
|
35
|
+
border-radius: 3px;
|
36
|
+
|
37
|
+
&[type=date] {
|
38
|
+
width: 150px;
|
39
|
+
}
|
40
|
+
}
|
41
|
+
|
42
|
+
span {
|
43
|
+
vertical-align: middle;
|
44
|
+
}
|
45
|
+
}
|
46
|
+
|
47
|
+
ul.nav {
|
48
|
+
margin-bottom: 0px;
|
49
|
+
|
50
|
+
li {
|
51
|
+
span.remove-wrapper {
|
52
|
+
display: inline-block;
|
53
|
+
width: 15px;
|
54
|
+
|
55
|
+
i.icon-remove {
|
56
|
+
margin-left: 5px;
|
57
|
+
cursor: pointer;
|
58
|
+
display: none;
|
59
|
+
}
|
60
|
+
}
|
61
|
+
|
62
|
+
&:hover span.remove-wrapper i.icon-remove {
|
63
|
+
display: inline-block;
|
64
|
+
}
|
65
|
+
}
|
66
|
+
}
|
67
|
+
|
68
|
+
.tabs {
|
69
|
+
padding: 10px;
|
70
|
+
|
71
|
+
>div {
|
72
|
+
overflow-y: scroll;
|
73
|
+
}
|
74
|
+
}
|
75
|
+
|
76
|
+
.query-status {
|
77
|
+
margin: 5px;
|
78
|
+
margin-top: 10px;
|
79
|
+
.error {
|
80
|
+
color: #C55050;
|
81
|
+
}
|
82
|
+
}
|
83
|
+
|
84
|
+
.row-viewer {
|
85
|
+
border-collapse: collapse;
|
86
|
+
|
87
|
+
td {
|
88
|
+
padding: 2px 4px;
|
89
|
+
vertical-align: top;
|
90
|
+
}
|
91
|
+
|
92
|
+
.column-controls {
|
93
|
+
i {
|
94
|
+
opacity: 0;
|
95
|
+
}
|
96
|
+
}
|
97
|
+
tr:hover {
|
98
|
+
.column-controls {
|
99
|
+
i {
|
100
|
+
opacity: 1;
|
101
|
+
}
|
102
|
+
cursor: pointer;
|
103
|
+
}
|
104
|
+
}
|
105
|
+
.column-ts {
|
106
|
+
white-space: nowrap;
|
107
|
+
.ts-milliseconds {
|
108
|
+
color: #777;
|
109
|
+
}
|
110
|
+
}
|
111
|
+
.column-host {
|
112
|
+
color: #333;
|
113
|
+
}
|
114
|
+
.column-pid, .column-rid {
|
115
|
+
color: rgb(163, 158, 56);
|
116
|
+
}
|
117
|
+
.column-severity {
|
118
|
+
&[data-severity=DEBUG] {
|
119
|
+
color: #999;
|
120
|
+
}
|
121
|
+
&[data-severity=INFO] {
|
122
|
+
color: rgb(135, 135, 176);
|
123
|
+
}
|
124
|
+
&[data-severity=WARN] {
|
125
|
+
color: yellow;
|
126
|
+
}
|
127
|
+
&[data-severity=ERROR] {
|
128
|
+
color: orange;
|
129
|
+
}
|
130
|
+
&[data-severity=FATAL] {
|
131
|
+
color: red;
|
132
|
+
}
|
133
|
+
}
|
134
|
+
.column-msg {
|
135
|
+
pre {
|
136
|
+
white-space: normal;
|
137
|
+
word-break: break-all;
|
138
|
+
margin: 0;
|
139
|
+
line-height: 16px;
|
140
|
+
}
|
141
|
+
}
|
142
|
+
|
143
|
+
.proximity-row {
|
144
|
+
display: none;
|
145
|
+
td {
|
146
|
+
background-color: #454545;
|
147
|
+
}
|
148
|
+
}
|
149
|
+
|
150
|
+
.collapsed-string {
|
151
|
+
display: none;
|
152
|
+
}
|
153
|
+
.expand-row {
|
154
|
+
padding-left: 10px;
|
155
|
+
color: #aaa;
|
156
|
+
}
|
157
|
+
|
158
|
+
.highlight-row {
|
159
|
+
background-color: #FFCCCC;
|
160
|
+
}
|
161
|
+
}
|
@@ -0,0 +1,29 @@
|
|
1
|
+
<!DOCTYPE HTML>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<meta http-equiv='Content-Type' content='text/html; charset=UTF-8'>
|
5
|
+
<title>Log viewer</title>
|
6
|
+
|
7
|
+
<%= stylesheet_link_tag 'big_query_log_viewer/application' %>
|
8
|
+
<%= javascript_include_tag 'big_query_log_viewer/application' %>
|
9
|
+
|
10
|
+
<script type='text/javascript'>
|
11
|
+
function init() {
|
12
|
+
var projectNumber = '<%= BigQueryLogViewer.project_number %>'
|
13
|
+
var clientId = '<%= BigQueryLogViewer.client_id %>'
|
14
|
+
var tablePrefix = '<%= BigQueryLogViewer.table_prefix %>'
|
15
|
+
var rowsPerPage = parseInt('<%= BigQueryLogViewer.rows_per_page %>')
|
16
|
+
BigQueryLogViewer.app = new BigQueryLogViewer.App(projectNumber, clientId, tablePrefix, rowsPerPage)
|
17
|
+
}
|
18
|
+
</script>
|
19
|
+
|
20
|
+
<%= javascript_include_tag 'https://apis.google.com/js/client.js?onload=init' %>
|
21
|
+
</head>
|
22
|
+
|
23
|
+
<body>
|
24
|
+
<div class='wrapper'>
|
25
|
+
<button id='authenticate-btn' class='btn btn-default'>Authenticate</button>
|
26
|
+
<div id='application'></div>
|
27
|
+
</div>
|
28
|
+
</body>
|
29
|
+
</html>
|
data/config/routes.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'rails/generators/base'
|
2
|
+
|
3
|
+
module BigQueryLogViewer
|
4
|
+
module Generators
|
5
|
+
class InstallGenerator < Rails::Generators::Base
|
6
|
+
source_root File.expand_path('../../templates', __FILE__)
|
7
|
+
|
8
|
+
desc 'Creates a BigQueryLogViewer initializer.'
|
9
|
+
|
10
|
+
def copy_initializer
|
11
|
+
template 'big_query_log_viewer.rb', 'config/initializers/big_query_log_viewer.rb'
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# Configuration settings for BigQueryLogViewer.
|
2
|
+
|
3
|
+
# Your client ID from the Google developer console.
|
4
|
+
BigQueryLogViewer.client_id = ''
|
5
|
+
|
6
|
+
# Your project number from the Google developer console.
|
7
|
+
BigQueryLogViewer.project_number = ''
|
8
|
+
|
9
|
+
# The prefix of your table.
|
10
|
+
# Assumes the suffix is the log date - so if each table is named as 'app_logs_YYYY-MM-DD'
|
11
|
+
# then you would put 'app_logs' here
|
12
|
+
BigQueryLogViewer.table_prefix = ''
|
13
|
+
|
14
|
+
# The number of rows to display on each results page.
|
15
|
+
BigQueryLogViewer.rows_per_page = 100
|
metadata
ADDED
@@ -0,0 +1,205 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: big_query_log_viewer
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.4
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Zach Schneider
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-09-03 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rails
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: therubyracer
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: coffee-rails
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: react-rails
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: less-rails
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: jquery-rails
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: font-awesome-rails
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :runtime
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: sprockets-coffee-react
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :runtime
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: less-rails-bootstrap
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :runtime
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: coffeelint
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - ">="
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
153
|
+
description: A simple Rails engine and React app to search logs stored in Google BigQuery.
|
154
|
+
email:
|
155
|
+
- zach@aha.io
|
156
|
+
executables: []
|
157
|
+
extensions: []
|
158
|
+
extra_rdoc_files: []
|
159
|
+
files:
|
160
|
+
- LICENSE.txt
|
161
|
+
- README.md
|
162
|
+
- Rakefile
|
163
|
+
- app/assets/javascripts/big_query_log_viewer/application.coffee
|
164
|
+
- app/assets/javascripts/big_query_log_viewer/components/pagination.coffee
|
165
|
+
- app/assets/javascripts/big_query_log_viewer/components/row.coffee
|
166
|
+
- app/assets/javascripts/big_query_log_viewer/components/search_box.coffee
|
167
|
+
- app/assets/javascripts/big_query_log_viewer/components/search_status.coffee
|
168
|
+
- app/assets/javascripts/big_query_log_viewer/components/tab.coffee
|
169
|
+
- app/assets/javascripts/big_query_log_viewer/components/tab_manager.coffee
|
170
|
+
- app/assets/javascripts/big_query_log_viewer/utils/query.coffee
|
171
|
+
- app/assets/stylesheets/big_query_log_viewer/application.css.less
|
172
|
+
- app/controllers/big_query_log_viewer/application_controller.rb
|
173
|
+
- app/views/big_query_log_viewer/application/index.html.erb
|
174
|
+
- config/coffeelint.json
|
175
|
+
- config/routes.rb
|
176
|
+
- lib/big_query_log_viewer.rb
|
177
|
+
- lib/big_query_log_viewer/engine.rb
|
178
|
+
- lib/big_query_log_viewer/version.rb
|
179
|
+
- lib/generators/big_query_log_viewer/install_generator.rb
|
180
|
+
- lib/generators/templates/big_query_log_viewer.rb
|
181
|
+
homepage: https://github.com/aha-app/bigquery-log-viewer
|
182
|
+
licenses:
|
183
|
+
- MIT
|
184
|
+
metadata: {}
|
185
|
+
post_install_message:
|
186
|
+
rdoc_options: []
|
187
|
+
require_paths:
|
188
|
+
- lib
|
189
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
190
|
+
requirements:
|
191
|
+
- - ">="
|
192
|
+
- !ruby/object:Gem::Version
|
193
|
+
version: '0'
|
194
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
195
|
+
requirements:
|
196
|
+
- - ">="
|
197
|
+
- !ruby/object:Gem::Version
|
198
|
+
version: '0'
|
199
|
+
requirements: []
|
200
|
+
rubyforge_project:
|
201
|
+
rubygems_version: 2.4.6
|
202
|
+
signing_key:
|
203
|
+
specification_version: 4
|
204
|
+
summary: A simple Rails engine and React app to search logs stored in Google BigQuery.
|
205
|
+
test_files: []
|