Initial commit
This commit is contained in:
commit
3cd6435536
9
.babelrc
Normal file
9
.babelrc
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"presets": [[
|
||||
"@babel/preset-env", {
|
||||
"modules": false,
|
||||
"shippedProposals": true
|
||||
}
|
||||
]],
|
||||
"plugins": ["@babel/transform-react-jsx"]
|
||||
}
|
||||
12
.eslintrc
Normal file
12
.eslintrc
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"extends": "standard",
|
||||
|
||||
"rules": {
|
||||
"comma-dangle": [ 2, "always-multiline" ],
|
||||
"no-unused-vars": [ 2, "all" ]
|
||||
},
|
||||
|
||||
"globals": {
|
||||
"fetch": true
|
||||
}
|
||||
}
|
||||
66
.gitignore
vendored
Normal file
66
.gitignore
vendored
Normal file
@ -0,0 +1,66 @@
|
||||
# from https://github.com/github/gitignore/blob/master/Node.gitignore
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Typescript v1 declaration files
|
||||
typings/
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
|
||||
# next.js build output
|
||||
.next
|
||||
|
||||
/dump
|
||||
/public
|
||||
9
browserslist
Normal file
9
browserslist
Normal file
@ -0,0 +1,9 @@
|
||||
# Define what browsers we support
|
||||
# https://github.com/ai/browserslist
|
||||
#
|
||||
# This is automatically sourced by babel-preset-env and autoprefixer (postcss)
|
||||
|
||||
> 10%
|
||||
last 2 versions
|
||||
iOS >= 9
|
||||
ie >= 11
|
||||
149
errors.txt
Normal file
149
errors.txt
Normal file
@ -0,0 +1,149 @@
|
||||
GhostingService.js?a92c:221 Uncaught TypeError: Cannot read property 'y' of null
|
||||
at isPlacedInPropping (GhostingService.js?a92c:221)
|
||||
at Array.filter (<anonymous>)
|
||||
at toggleProppingOnCollidingElement (GhostingService.js?a92c:220)
|
||||
at Object.removeGhost (GhostingService.js?a92c:57)
|
||||
at Object.out (DropAreas.js?510e:307)
|
||||
at eval (DnDLib.js?b5c7:55)
|
||||
at fireUntilImmediateStopped (interact.js?f3bb:47)
|
||||
at Eventable.fire (interact.js?f3bb:65)
|
||||
at Interactable.fire (interact.js?f3bb:517)
|
||||
at fireDropEvents (interact.js?f3bb:1833)
|
||||
|
||||
|
||||
margin‒
|
||||
‒border‒
|
||||
‒padding‒
|
||||
‒2533 × 1941.970‒
|
||||
‒‒
|
||||
‒‒
|
||||
‒
|
||||

|
||||
Console
|
||||

|
||||

|
||||

|
||||

|
||||
top
|
||||

|
||||
All levels
|
||||

|
||||
Group similar
|
||||

|
||||
|
||||
angular.js:18104 Uncaught RangeError: Maximum call stack size exceeded
|
||||
at Scope.$apply (angular.js:18104)
|
||||
at Scope.$delegate.$safeApply (safeApply.js?2440:19)
|
||||
at Object.dragEnd [as end] (MoveUnits.js?949c:183)
|
||||
at eval (DnDLib.js?f6dd:104)
|
||||
at fireUntilImmediateStopped (interact.js?f3bb:47)
|
||||
at Eventable.fire (interact.js?f3bb:65)
|
||||
at Interactable.fire (interact.js?f3bb:517)
|
||||
at firePrepared (interact.js?f3bb:1376)
|
||||
at eval (interact.js?f3bb:1368)
|
||||
at Signals.fire (interact.js?f3bb:5768)
|
||||
|
||||
interact.js?f3bb:1177 [Violation] 'pointerup' handler took 617ms
|
||||
|
||||
angular.js:18249 Uncaught RangeError: Maximum call stack size exceeded
|
||||
at Scope.$emit (angular.js:18249)
|
||||
at Scope.$rootScope.(anonymous function) [as $emit] (webpack-internal:///161:338:23)
|
||||
at Object.dragEnd [as end] (MoveUnits.js?949c:181)
|
||||
at eval (DnDLib.js?f6dd:104)
|
||||
at fireUntilImmediateStopped (interact.js?f3bb:47)
|
||||
at Eventable.fire (interact.js?f3bb:65)
|
||||
at Interactable.fire (interact.js?f3bb:517)
|
||||
at firePrepared (interact.js?f3bb:1376)
|
||||
at eval (interact.js?f3bb:1368)
|
||||
at Signals.fire (interact.js?f3bb:5768)
|
||||
|
||||
jquery.js:4544 [Violation] 'mouseup' handler took 392ms
|
||||
|
||||
angular.js:14199 Error: [$rootScope:inprog] $digest already in progress
|
||||
|
||||
http://errors.angularjs.org/1.5.11/$rootScope/inprog?p0=NaNigest
|
||||
at https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.11/angular.js:68:12
|
||||
at beginPhase (https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.11/angular.js:18357:15)
|
||||
at Scope.$apply (https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.11/angular.js:18092:11)
|
||||
at HTMLDivElement.<anonymous> (https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.11/angular.js:26597:23)
|
||||
at HTMLDivElement.dispatch (https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.4/jquery.js:4737:27)
|
||||
at HTMLDivElement.elemData.handle (https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.4/jquery.js:4549:28)
|
||||
angular.js:14199 Error: [$rootScope:inprog] $digest already in progress
|
||||
http://errors.angularjs.org/1.5.11/$rootScope/inprog?p0=NaNigest
|
||||
at https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.11/angular.js:68:12
|
||||
at beginPhase (https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.11/angular.js:18357:15)
|
||||
at Scope.$digest (https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.11/angular.js:17785:9)
|
||||
at Scope.$apply (https://cdnjs.cloudflare.com/ajax/libs/a
|
||||
12
|
||||
[Violation] 'pointermove' handler took <N>ms
|
||||
angular.js:18249 Uncaught RangeError: Maximum call stack size exceeded
|
||||
at Scope.$emit (angular.js:18249)
|
||||
at Scope.$rootScope.(anonymous function) [as $emit] (webpack-internal:///161:338:23)
|
||||
at Object.dragEnd [as end] (MoveUnits.js?949c:181)
|
||||
at eval (DnDLib.js?f6dd:104)
|
||||
at fireUntilImmediateStopped (interact.js?f3bb:47)
|
||||
at Eventable.fire (interact.js?f3bb:65)
|
||||
at Interactable.fire (interact.js?f3bb:517)
|
||||
at firePrepared (interact.js?f3bb:1376)
|
||||
at eval (interact.js?f3bb:1368)
|
||||
at Signals.fire (interact.js?f3bb:5768)
|
||||
6
|
||||
[Violation] 'pointerup' handler took <N>ms
|
||||
angular.js:18249 Uncaught RangeError: Maximum call stack size exceeded
|
||||
at Scope.$emit (angular.js:18249)
|
||||
at Scope.$rootScope.(anonymous function) [as $emit] (webpack-internal:///161:338:23)
|
||||
at Object.dragEnd [as end] (MoveUnits.js?949c:181)
|
||||
at eval (DnDLib.js?f6dd:104)
|
||||
at fireUntilImmediateStopped (interact.js?f3bb:47)
|
||||
at Eventable.fire (interact.js?f3bb:65)
|
||||
at Interactable.fire (interact.js?f3bb:517)
|
||||
at firePrepared (interact.js?f3bb:1376)
|
||||
at eval (interact.js?f3bb:1368)
|
||||
at Signals.fire (interact.js?f3bb:5768)
|
||||
6
|
||||
[Violation] 'mouseup' handler took <N>ms
|
||||
[Violation] Forced reflow while executing JavaScript took 34ms
|
||||
jquery.js:5487 Uncaught RangeError: Maximum call stack size exceeded
|
||||
at HTMLLIElement.<anonymous> (jquery.js:5487)
|
||||
at jquery.js:142
|
||||
at Function.map (jquery.js:452)
|
||||
at jQuery.fn.init.map (jquery.js:141)
|
||||
at jQuery.fn.init.clone (jquery.js:5486)
|
||||
at Object.removeGhost (GhostingService.js?882c:52)
|
||||
at Object.out (DropAreas.js?510e:307)
|
||||
at eval (DnDLib.js?f6dd:50)
|
||||
at fireUntilImmediateStopped (interact.js?f3bb:47)
|
||||
at Eventable.fire (interact.js?f3bb:65)
|
||||
angular.js:18249 Uncaught RangeError: Maximum call stack size exceeded
|
||||
at Scope.$emit (angular.js:18249)
|
||||
at Scope.$rootScope.(anonymous function) [as $emit] (webpack-internal:///161:338:23)
|
||||
at Object.dragEnd [as end] (MoveUnits.js?949c:181)
|
||||
at eval (DnDLib.js?f6dd:104)
|
||||
at fireUntilImmediateStopped (interact.js?f3bb:47)
|
||||
at Eventable.fire (interact.js?f3bb:65)
|
||||
at Interactable.fire (interact.js?f3bb:517)
|
||||
at firePrepared (interact.js?f3bb:1376)
|
||||
at eval (interact.js?f3bb:1368)
|
||||
at Signals.fire (interact.js?f3bb:5768)
|
||||
angular.js:18104 Uncaught RangeError: Maximum call stack size exceeded
|
||||
at Scope.$apply (angular.js:18104)
|
||||
at Scope.$delegate.$safeApply (safeApply.js?2440:19)
|
||||
at Object.dragEnd [as end] (MoveUnits.js?949c:183)
|
||||
at eval (DnDLib.js?f6dd:104)
|
||||
at fireUntilImmediateStopped (interact.js?f3bb:47)
|
||||
at Eventable.fire (interact.js?f3bb:65)
|
||||
at Interactable.fire (interact.js?f3bb:517)
|
||||
at firePrepared (interact.js?f3bb:1376)
|
||||
at eval (interact.js?f3bb:1368)
|
||||
at Signals.fire (interact.js?f3bb:5768)
|
||||
angular.js:18249 Uncaught RangeError: Maximum call stack size exceeded
|
||||
at Scope.$emit (angular.js:18249)
|
||||
at Scope.$rootScope.(anonymous function) [as $emit] (webpack-internal:///161:338:23)
|
||||
at Object.dragEnd [as end] (MoveUnits.js?949c:181)
|
||||
at eval (DnDLib.js?f6dd:104)
|
||||
at fireUntilImmediateStopped (interact.js?f3bb:47)
|
||||
at Eventable.fire (interact.js?f3bb:65)
|
||||
at Interactable.fire (interact.js?f3bb:517)
|
||||
at firePrepared (interact.js?f3bb:1376)
|
||||
at eval (interact.js?f3bb:1368)
|
||||
at Signals.fire (interact.js?f3bb:5768)
|
||||
3
nodemon.json
Normal file
3
nodemon.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"ext": "js,ejs"
|
||||
}
|
||||
14270
package-lock.json
generated
Normal file
14270
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
55
package.json
Normal file
55
package.json
Normal file
@ -0,0 +1,55 @@
|
||||
{
|
||||
"name": "stack-trace-mapper",
|
||||
"version": "0.0.1",
|
||||
"description": "",
|
||||
"main": "server/server.js",
|
||||
"scripts": {
|
||||
"lint": "eslint .",
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"webpack": "webpack"
|
||||
},
|
||||
"author": "Linus Miller <lohfu@lohfu.io> (https://lohfu.io/)",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@bmp/console": "0.0.5",
|
||||
"@bmp/highlight-stack": "0.0.2",
|
||||
"body-parser": "^1.18.3",
|
||||
"classnames": "^2.2.5",
|
||||
"ejs": "^2.6.1",
|
||||
"express": "^4.16.3",
|
||||
"lowline": "^0.2.2",
|
||||
"morgan": "^1.9.0",
|
||||
"multer": "^1.3.0",
|
||||
"pg": "^7.4.3",
|
||||
"react": "^16.4.0",
|
||||
"react-dom": "^16.4.0",
|
||||
"react-router": "^4.2.0",
|
||||
"react-router-dom": "^4.2.2",
|
||||
"source-map": "^0.7.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "^7.0.0-beta.49",
|
||||
"@babel/core": "^7.0.0-beta.49",
|
||||
"@babel/plugin-transform-react-jsx": "^7.0.0-beta.49",
|
||||
"@babel/preset-env": "^7.0.0-beta.49",
|
||||
"autoprefixer": "^8.5.1",
|
||||
"babel-loader": "^8.0.0-beta.2",
|
||||
"css-loader": "^0.28.11",
|
||||
"eslint": "^4.19.1",
|
||||
"eslint-config-standard": "^11.0.0",
|
||||
"eslint-config-standard-react": "^6.0.0",
|
||||
"eslint-plugin-import": "^2.12.0",
|
||||
"eslint-plugin-node": "^6.0.1",
|
||||
"eslint-plugin-promise": "^3.8.0",
|
||||
"eslint-plugin-react": "^7.8.2",
|
||||
"eslint-plugin-standard": "^3.1.0",
|
||||
"extract-text-webpack-plugin": "^4.0.0-beta.0",
|
||||
"postcss-color-function": "^4.0.1",
|
||||
"postcss-loader": "^2.1.5",
|
||||
"precss": "^3.1.2",
|
||||
"style-loader": "^0.21.0",
|
||||
"webpack": "^4.9.1",
|
||||
"webpack-cli": "^2.1.4",
|
||||
"webpack-dev-middleware": "^3.1.3"
|
||||
}
|
||||
}
|
||||
25
postcss.config.js
Normal file
25
postcss.config.js
Normal file
@ -0,0 +1,25 @@
|
||||
'use strict'
|
||||
|
||||
const env = process.env.NODE_ENV || 'development'
|
||||
|
||||
const config = {
|
||||
development: {
|
||||
plugins: {
|
||||
precss: {},
|
||||
autoprefixer: {},
|
||||
'postcss-color-function': {},
|
||||
},
|
||||
},
|
||||
|
||||
production: {
|
||||
plugins: {
|
||||
precss: {},
|
||||
autoprefixer: {},
|
||||
cssnano: {
|
||||
preset: 'default',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
module.exports = config[env]
|
||||
13
server/config/postgres.js
Normal file
13
server/config/postgres.js
Normal file
@ -0,0 +1,13 @@
|
||||
'use strict'
|
||||
|
||||
module.exports = {
|
||||
database: 'stack_trace_mapper', // process.env.NODE_ENV var: PGDATABASE
|
||||
user: 'stack_trace_mapper',
|
||||
password: 'thesesourcemapsaregoingtochangetheworld',
|
||||
// host: 'hq.bitmill.co', // Server hosting the postgres database
|
||||
host: 'localhost', // Server hosting the postgres database
|
||||
// port: 6543, // process.env.NODE_ENV var: PGPORT
|
||||
port: 5432, // process.env.NODE_ENV var: PGPORT
|
||||
max: 10, // max number of clients in the pool
|
||||
idleTimeoutMillis: 30000, // how long a client is allowed to remain idle before being closed
|
||||
}
|
||||
12
server/db.js
Normal file
12
server/db.js
Normal file
@ -0,0 +1,12 @@
|
||||
'use strict'
|
||||
|
||||
const { Pool } = require('pg')
|
||||
const config = require('./config/postgres')
|
||||
|
||||
const pool = new Pool(config)
|
||||
|
||||
module.exports = {
|
||||
query: (text, params, callback) => {
|
||||
return pool.query(text, params, callback)
|
||||
},
|
||||
}
|
||||
41
server/handlers/source-maps.js
Normal file
41
server/handlers/source-maps.js
Normal file
@ -0,0 +1,41 @@
|
||||
'use strict'
|
||||
|
||||
const db = require('../db')
|
||||
|
||||
function create (sourceMap) {
|
||||
return db.query(
|
||||
'INSERT INTO source_maps(application, version, ui_platform, content) VALUES($1, $2, $3, $4) RETURNING *;',
|
||||
[ sourceMap.application, sourceMap.version, sourceMap.uiPlatform, sourceMap.content ]
|
||||
).then((result) => result.rows[0])
|
||||
}
|
||||
|
||||
function listAll () {
|
||||
return db
|
||||
.query('SELECT id, application, version, ui_platform as "uiPlatform", created_at as "createdAt" FROM source_maps;')
|
||||
.then((result) => result.rows)
|
||||
}
|
||||
|
||||
function getAll () {
|
||||
return db.query('SELECT * FROM source_maps').then((result) => result.rows)
|
||||
}
|
||||
|
||||
function get (sourceMap) {
|
||||
const values = [ sourceMap.application, sourceMap.version, sourceMap.uiPlatform ]
|
||||
|
||||
return db
|
||||
.query('SELECT * FROM source_maps WHERE application = $1 AND version = $2 AND ui_platform = $3', values)
|
||||
.then((result) => {
|
||||
if (result.rowCount <= 0) {
|
||||
throw new Error('No source map found')
|
||||
}
|
||||
|
||||
return result.rows[0]
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
create,
|
||||
get,
|
||||
getAll,
|
||||
listAll,
|
||||
}
|
||||
9
server/middleware/multer.js
Normal file
9
server/middleware/multer.js
Normal file
@ -0,0 +1,9 @@
|
||||
'use strict'
|
||||
|
||||
const multer = require('multer')
|
||||
|
||||
const memoryStorage = multer.memoryStorage()
|
||||
|
||||
const upload = multer({ storage: memoryStorage })
|
||||
|
||||
module.exports = upload
|
||||
62
server/middleware/source-maps.js
Normal file
62
server/middleware/source-maps.js
Normal file
@ -0,0 +1,62 @@
|
||||
'use strict'
|
||||
|
||||
const _ = require('lodash')
|
||||
const remapper = require('../util/remap')
|
||||
const handlers = require('../handlers/source-maps')
|
||||
|
||||
const { SourceMapConsumer } = require('source-map')
|
||||
|
||||
function create (req, res) {
|
||||
const sourceMap = _.mapKeys(req.body, (val, key) => _.camelCase(key))
|
||||
|
||||
if (!sourceMap.content && req.file) {
|
||||
sourceMap.content = req.file.buffer.toString('utf8')
|
||||
}
|
||||
|
||||
console.log(sourceMap)
|
||||
|
||||
handlers.create(sourceMap).then(() => {
|
||||
res.render('index')
|
||||
})
|
||||
}
|
||||
|
||||
function getOne (req, res, next) {
|
||||
next()
|
||||
}
|
||||
|
||||
function listAll (req, res, next) {
|
||||
handlers.listAll().then((sourceMaps) => {
|
||||
res.locals.sourceMaps = sourceMaps
|
||||
|
||||
res.json(sourceMaps)
|
||||
}).catch(next)
|
||||
}
|
||||
|
||||
function remap (req, res, next) {
|
||||
const body = _.mapKeys(req.body, (val, key) => _.camelCase(key))
|
||||
|
||||
const promise = body.content ? Promise.resolve(body) : handlers.get(body)
|
||||
|
||||
promise
|
||||
.then((sourceMap) => new SourceMapConsumer(sourceMap.content))
|
||||
.then((consumer) => {
|
||||
const remapped = remapper(body.stack, consumer)
|
||||
|
||||
consumer.destroy()
|
||||
|
||||
if (req.accepts([ 'json', 'html' ]) === 'json') {
|
||||
res.json(remapped)
|
||||
} else {
|
||||
res.locals.stack = remapped
|
||||
res.render('remap-result')
|
||||
}
|
||||
})
|
||||
.catch(next)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
remap,
|
||||
create,
|
||||
getOne,
|
||||
listAll,
|
||||
}
|
||||
24
server/routes.js
Normal file
24
server/routes.js
Normal file
@ -0,0 +1,24 @@
|
||||
'use strict'
|
||||
|
||||
const upload = require('./middleware/multer')
|
||||
const sourceMaps = require('./middleware/source-maps')
|
||||
|
||||
module.exports = (server) => {
|
||||
server.get('/:page?', (req, res) => {
|
||||
res.render('index')
|
||||
})
|
||||
|
||||
// server.get('/upload', sourceMaps.listAll, (req, res) => {
|
||||
// res.render('upload')
|
||||
// })
|
||||
|
||||
// server.get('/remap', sourceMaps.listAll, (req, res) => {
|
||||
// res.render('remap')
|
||||
// })
|
||||
|
||||
server.get('/api/source-maps', sourceMaps.listAll)
|
||||
server.post('/api/source-maps', upload.single('map'), sourceMaps.create)
|
||||
server.get('/api/source-maps/:id', sourceMaps.getOne)
|
||||
|
||||
server.post('/api/remap', sourceMaps.remap)
|
||||
}
|
||||
61
server/server.js
Normal file
61
server/server.js
Normal file
@ -0,0 +1,61 @@
|
||||
'use strict'
|
||||
|
||||
process.env.NODE_ENV = process.env.NODE_ENV || 'development'
|
||||
|
||||
// modules > native
|
||||
const path = require('path')
|
||||
|
||||
// modules > 3rd party
|
||||
const express = require('express')
|
||||
const bodyParser = require('body-parser')
|
||||
const chalk = require('chalk')
|
||||
const highlightStack = require('@bmp/highlight-stack')
|
||||
|
||||
process.on('uncaughtException', (err) => {
|
||||
console.error(chalk.red('UNCAUGHT EXCEPTION'))
|
||||
if (err.stack) {
|
||||
console.error(highlightStack(err.stack))
|
||||
} else {
|
||||
console.error(err)
|
||||
}
|
||||
process.exit(1)
|
||||
})
|
||||
|
||||
const server = express()
|
||||
|
||||
// set template engine
|
||||
server.set('view engine', 'ejs')
|
||||
server.set('views', path.join(__dirname, 'templates'))
|
||||
server.use('/', express.static('public'))
|
||||
|
||||
// set up console logs in dev mode
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
require('@bmp/console')({ log: true, error: true, dir: true })
|
||||
|
||||
const morgan = require('morgan')
|
||||
|
||||
server.use(morgan('dev'))
|
||||
|
||||
const webpackMiddleware = require('webpack-dev-middleware')
|
||||
const webpack = require('webpack')
|
||||
const webpackConfig = require('../webpack.config')
|
||||
|
||||
server.use(webpackMiddleware(webpack(webpackConfig)))
|
||||
}
|
||||
|
||||
server.use(bodyParser.json())
|
||||
server.use(bodyParser.urlencoded({ extended: true }))
|
||||
|
||||
// server.use('/', (req, res, next) => {
|
||||
// console.log('hello')
|
||||
// res.send('<h1>Hello</h1>')
|
||||
// })
|
||||
|
||||
const port = process.env.port || 1337
|
||||
|
||||
console.log(path.resolve(__dirname, '../'))
|
||||
require('./routes')(server)
|
||||
|
||||
server.listen(port, () => {
|
||||
console.info(`[${chalk.cyan('INIT')}] HTTP Server listening on port ${chalk.magenta(port)} (${chalk.yellow(server.get('env'))})`)
|
||||
})
|
||||
4
server/templates/footer.ejs
Normal file
4
server/templates/footer.ejs
Normal file
@ -0,0 +1,4 @@
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
14
server/templates/header.ejs
Normal file
14
server/templates/header.ejs
Normal file
@ -0,0 +1,14 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<title>Cybercom Sourcemap Magic</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="/css/main.css" />
|
||||
<!-- <script src="/js/app.mjs" type="module"></script> -->
|
||||
<!-- <script src="/bundle.js" type="module"></script> -->
|
||||
</head>
|
||||
|
||||
<body>
|
||||
21
server/templates/index.ejs
Normal file
21
server/templates/index.ejs
Normal file
@ -0,0 +1,21 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>Cybercom Stack Trace Mapper</title>
|
||||
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<link rel="stylesheet" href="/css/bundle.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id='app'></div>
|
||||
|
||||
<script src='/js/bundle.js'></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
5
server/templates/remap-result.ejs
Normal file
5
server/templates/remap-result.ejs
Normal file
@ -0,0 +1,5 @@
|
||||
<h1>Remapped</h1>
|
||||
|
||||
<pre class='mapped-stack'>
|
||||
<%- stack.join('\n') %>
|
||||
</pre>
|
||||
35
server/templates/remap.ejs
Normal file
35
server/templates/remap.ejs
Normal file
@ -0,0 +1,35 @@
|
||||
<% include header %>
|
||||
|
||||
<div class="remap page">
|
||||
<h1>Remap Stack Trace</h1>
|
||||
|
||||
<h2>Original</h2>
|
||||
|
||||
<form action="/api/remap" method="POST">
|
||||
<div class="row">
|
||||
<label>Application</label>
|
||||
<input type="text" name="application" placeholder="Application" value="algot" />
|
||||
</div>
|
||||
<div class="row">
|
||||
<label>UI Platform</label>
|
||||
<input type="text" name="ui_platform" placeholder="UI Platform" value="IRW" />
|
||||
</div class="row">
|
||||
<div class="row">
|
||||
<label>Version</label>
|
||||
<input type="text" name="version" placeholder="Version" value="4.3.0-alpha.0" />
|
||||
</div>
|
||||
<div class="row">
|
||||
<label>Stack</label>
|
||||
<textarea name="stack"></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<button type="submit">Apply</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<pre class='original-stack'></pre>
|
||||
|
||||
<h2>Remapped</h2>
|
||||
|
||||
<pre class='remapped-stack'></pre>
|
||||
</div>
|
||||
40
server/templates/upload.ejs
Normal file
40
server/templates/upload.ejs
Normal file
@ -0,0 +1,40 @@
|
||||
<% include header %>
|
||||
|
||||
<h2>Upload a source map</h2>
|
||||
|
||||
<div class="upload page">
|
||||
<form action='/api/upload' method='POST' enctype='multipart/form-data'>
|
||||
<div>
|
||||
<label for='application'>Application</label>
|
||||
<input type='text' name='application' placeholder='Application' value='algot' />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for='application'>UI</label>
|
||||
<input type='text' name='uiPlatform' placeholder='UI' />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for='application'>Version</label>
|
||||
<input type='text' name='version' placeholder='Version' value='4.3.0' />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<input type='file' name='map' required />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button type='submit'>Upload</button>
|
||||
</div>
|
||||
</form>
|
||||
<div class="source-map-list">
|
||||
<ul>
|
||||
<% for (const sourceMap of sourceMaps) { %>
|
||||
<li><%- sourceMap.application %> <%- sourceMap.version %> <%- sourceMap.uiPlatform %></li>
|
||||
<% } %>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/js/upload.mjs"></script>
|
||||
<% include footer %>
|
||||
62
server/test-convert.js
Normal file
62
server/test-convert.js
Normal file
@ -0,0 +1,62 @@
|
||||
'use strict'
|
||||
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
|
||||
// const sourceMap = require('source-map');
|
||||
const { SourceMapConsumer } = require('source-map')
|
||||
const convert = require('./convert')
|
||||
|
||||
// const map = fs.readFileSync(path.join(__dirname, 'algot-irw/isa.js.map'), 'utf8');
|
||||
const map = fs.readFileSync(path.join(__dirname, 'algot-irw/isa.js.map'), 'utf8')
|
||||
|
||||
// console.log(map);
|
||||
|
||||
const str = `
|
||||
Error
|
||||
at Object._ [as removeGhost] (isa.js:120)
|
||||
at Object.i [as out] (isa.js:120)
|
||||
at isa.js:120
|
||||
at r (isa.js:24)
|
||||
at e.fire (isa.js:24)
|
||||
at e.fire (isa.js:24)
|
||||
at l (isa.js:24)
|
||||
at isa.js:24
|
||||
at e.fire (isa.js:24)
|
||||
at e.doMove (isa.js:24)
|
||||
`
|
||||
|
||||
const chrome = `Error: Testing error
|
||||
at Object.y [as addGhost] (http://localhost:8080/algot/irw/js/isa.js:120:377375)
|
||||
at Object.n [as over] (http://localhost:8080/algot/irw/js/isa.js:120:263605)
|
||||
at http://localhost:8080/algot/irw/js/isa.js:120:373131
|
||||
at r (http://localhost:8080/algot/irw/js/isa.js:24:111023)
|
||||
at e.fire (http://localhost:8080/algot/irw/js/isa.js:24:111215)
|
||||
at e.fire (http://localhost:8080/algot/irw/js/isa.js:24:116563)
|
||||
at l (http://localhost:8080/algot/irw/js/isa.js:24:131058)
|
||||
at http://localhost:8080/algot/irw/js/isa.js:24:132002
|
||||
at e.fire (http://localhost:8080/algot/irw/js/isa.js:24:178230)
|
||||
at e.doMove (http://localhost:8080/algot/irw/js/isa.js:24:123026)
|
||||
`
|
||||
|
||||
const firefox = `
|
||||
y@http://localhost:8080/algot/irw/js/isa.js:120:377375
|
||||
n@http://localhost:8080/algot/irw/js/isa.js:120:263603
|
||||
r/<@http://localhost:8080/algot/irw/js/isa.js:120:373129
|
||||
r@http://localhost:8080/algot/irw/js/isa.js:24:111023
|
||||
[2]</a</e.prototype.fire@http://localhost:8080/algot/irw/js/isa.js:24:111215
|
||||
[4]</M</e.prototype.fire@http://localhost:8080/algot/irw/js/isa.js:24:116551
|
||||
l@http://localhost:8080/algot/irw/js/isa.js:24:131056
|
||||
[8]</<@http://localhost:8080/algot/irw/js/isa.js:24:132002
|
||||
[34]</r</e.prototype.fire@http://localhost:8080/algot/irw/js/isa.js:24:178230
|
||||
[5]</v</e.prototype.doMove@http://localhost:8080/algot/irw/js/isa.js:24:123024
|
||||
[5]</v</e.prototype.pointerMove@http://localhost:8080/algot/irw/js/isa.js:24:122745
|
||||
r/<@http://localhost:8080/algot/irw/js/isa.js:24:120083`
|
||||
|
||||
// const chrome = 'Error: Testing error at Object.y [as addGhost] (http://localhost:8080/algot/irw/js/isa.js:120:377375) at Object.n [as over] (http://localhost:8080/algot/irw/js/isa.js:120:263605) at http://localhost:8080/algot/irw/js/isa.js:120:373131 at r (http://localhost:8080/algot/irw/js/isa.js:24:111023) at e.fire (http://localhost:8080/algot/irw/js/isa.js:24:111215) at e.fire (http://localhost:8080/algot/irw/js/isa.js:24:116563) at l (http://localhost:8080/algot/irw/js/isa.js:24:131058) at http://localhost:8080/algot/irw/js/isa.js:24:132002 at e.fire (http://localhost:8080/algot/irw/js/isa.js:24:178230) at e.doMove (http://localhost:8080/algot/irw/js/isa.js:24:123026)'
|
||||
|
||||
SourceMapConsumer.with(map, null, (consumer) => {
|
||||
console.log(convert(chrome, consumer))
|
||||
console.log('\n\n\n')
|
||||
console.log(convert(firefox, consumer))
|
||||
})
|
||||
84
server/util/remap.js
Normal file
84
server/util/remap.js
Normal file
@ -0,0 +1,84 @@
|
||||
'use strict'
|
||||
|
||||
const { SourceMapConsumer } = require('source-map')
|
||||
|
||||
module.exports = function stackTraceMapper (stack, map) {
|
||||
if (!(map instanceof SourceMapConsumer)) {
|
||||
throw new Error('map is not a consumer')
|
||||
}
|
||||
|
||||
const setups = [
|
||||
{
|
||||
// settings for Chrome, Edge and IE (Plus?)
|
||||
regex: /\s+at(?:\s+([^\s]+))?\s\(?([/a-z0-9:.-]+):([0-9]+):([0-9]+)/,
|
||||
// regex: /\s+at(?:\s+([^)]+))?\s\(?([/a-z0-9:.-]+):([0-9]+):([0-9]+)/,
|
||||
skip: 1,
|
||||
split: /\n?(?= at )/,
|
||||
},
|
||||
|
||||
{
|
||||
// settings for Firefox and Safari
|
||||
regex: /([a-zA-Z0-9]*)@(.*):([0-9]+):([0-9]+)/,
|
||||
skip: 0,
|
||||
split: /\s+/,
|
||||
},
|
||||
]
|
||||
|
||||
const setup = setups.find((setup) => {
|
||||
return setup.regex.test(stack)
|
||||
})
|
||||
|
||||
if (!setup) {
|
||||
throw new Error('Stack trace is not recognised')
|
||||
}
|
||||
|
||||
const split = stack.trim().split(setup.split)
|
||||
// TODO whether the error name and similar preludes the stack varies between browsers perhaps we should create one
|
||||
const pre = split.slice(0, setup.skip)
|
||||
console.log('split', split)
|
||||
console.log('pre', pre)
|
||||
|
||||
const lines = split.slice(setup.skip).map((source) => {
|
||||
// TODO handle lines that have not matched
|
||||
const match = source.match(setup.regex)
|
||||
|
||||
if (!match) {
|
||||
return { source }
|
||||
}
|
||||
|
||||
const [ , fnc, file, line, column ] = match
|
||||
|
||||
return { fnc, file, line: parseInt(line), column: parseInt(column), source }
|
||||
})
|
||||
|
||||
const originalLines = lines.map((obj) => {
|
||||
if (obj.line) {
|
||||
return map.originalPositionFor({ line: obj.line, column: obj.column })
|
||||
}
|
||||
|
||||
return obj
|
||||
})
|
||||
|
||||
// need to do research as to why, but the original line objects returned
|
||||
// contain the function call names are shifted one line compared with
|
||||
// the sourcemapped stack printed in chrome dev tools
|
||||
const adjustedLines = originalLines.map((obj, index, arr) => {
|
||||
if (obj.line) {
|
||||
const prev = arr[index + 1]
|
||||
|
||||
obj.name = (prev && prev.name) || undefined
|
||||
}
|
||||
|
||||
return obj
|
||||
})
|
||||
|
||||
return pre.concat(adjustedLines.map(formatLine))
|
||||
}
|
||||
|
||||
function formatLine ({ source, line, column, name = '(unknown)' }) {
|
||||
if (!line || !column) {
|
||||
return ` ${source}`
|
||||
}
|
||||
|
||||
return ` at ${name} (${source}:${line}:${column})`
|
||||
}
|
||||
12
src/.eslintrc
Normal file
12
src/.eslintrc
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"extends": [
|
||||
"../../.eslintrc",
|
||||
"standard-react"
|
||||
],
|
||||
|
||||
"globals": {
|
||||
"fetch": true,
|
||||
"google": true,
|
||||
"INITIAL_CONTEXT": true
|
||||
}
|
||||
}
|
||||
39
src/components/Layout.jsx
Normal file
39
src/components/Layout.jsx
Normal file
@ -0,0 +1,39 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { get } from 'lowline'
|
||||
|
||||
import Navigation from './Navigation'
|
||||
|
||||
class Layout extends React.Component {
|
||||
componentDidUpdate () {
|
||||
console.log('this did update')
|
||||
const { location } = this.props
|
||||
|
||||
const offset = get(window.history, 'state.scrollTop') || 0
|
||||
|
||||
window.scrollTo(0, offset)
|
||||
}
|
||||
|
||||
render () {
|
||||
// const { page, navigation, location, organization, children } = this.props
|
||||
const { routes, children } = this.props
|
||||
|
||||
return (
|
||||
<div className='container'>
|
||||
<header>
|
||||
<h1><a href='/'>Stack Trace Mapper</a></h1>
|
||||
|
||||
<Navigation routes={routes} />
|
||||
</header>
|
||||
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Layout.propTypes = {
|
||||
routes: PropTypes.array,
|
||||
}
|
||||
|
||||
export default Layout
|
||||
88
src/components/Navigation.jsx
Normal file
88
src/components/Navigation.jsx
Normal file
@ -0,0 +1,88 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import classNames from 'classnames'
|
||||
import { bindAll } from 'lowline'
|
||||
|
||||
import NavigationItem from './NavigationItem'
|
||||
|
||||
class Navigation extends React.Component {
|
||||
constructor (options = {}) {
|
||||
super(options)
|
||||
|
||||
this.state = { visible: false }
|
||||
|
||||
bindAll(this, ['hide', 'toggle'])
|
||||
}
|
||||
|
||||
// componentWillReceiveProps ({ location }) {
|
||||
// if (this.props.location.pathname !== location.pathname && this.state.visible) {
|
||||
// this.setState({
|
||||
// visible: false,
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
|
||||
// shouldComponentUpdate ({ location }, nextState) {
|
||||
// return nextState.visible !== this.state.visible || this.props.location.pathname !== location.pathname
|
||||
// }
|
||||
|
||||
// componentDidUpdate ({ location }) {
|
||||
// if (this.props.location !== location) {
|
||||
|
||||
// }
|
||||
|
||||
// }
|
||||
|
||||
toggle () {
|
||||
if (!this.state.visible) {
|
||||
this.show()
|
||||
} else {
|
||||
this.hide()
|
||||
}
|
||||
}
|
||||
|
||||
show () {
|
||||
if (!this.state.visible) {
|
||||
document.body.classList.add('shift')
|
||||
|
||||
this.setState({
|
||||
visible: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
hide () {
|
||||
if (this.state.visible) {
|
||||
document.body.classList.remove('shift')
|
||||
|
||||
this.setState({
|
||||
visible: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const { routes, location } = this.props
|
||||
const { visible } = this.state
|
||||
|
||||
return (
|
||||
<nav className={classNames({visible})}>
|
||||
<button onClick={this.toggle}>
|
||||
<div className='bar' />
|
||||
<div className='bar' />
|
||||
<div className='bar' />
|
||||
</button>
|
||||
<ul onClick={this.hide}>
|
||||
{routes && routes.map((route) => <NavigationItem key={route.name} location={location} {...route} />)}
|
||||
</ul>
|
||||
{this.state.visible && <div onClick={this.hide} className='cover' />}
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Navigation.propTypes = {
|
||||
routes: PropTypes.array,
|
||||
}
|
||||
|
||||
export default Navigation
|
||||
12
src/components/NavigationItem.jsx
Normal file
12
src/components/NavigationItem.jsx
Normal file
@ -0,0 +1,12 @@
|
||||
import React from 'react'
|
||||
import classNames from 'classnames'
|
||||
import { Link } from 'react-router-dom'
|
||||
|
||||
const NavigationItem = ({ location, name, title, path, routes }) => (
|
||||
<li className={classNames({ [name]: true, current: location && (location.pathname === path || (path !== '/' && location.pathname.startsWith(path))) })}>
|
||||
<Link to={path}><span>{title}</span></Link>
|
||||
{routes && <ul>{routes.map((route) => route.nav !== false && <NavigationItem key={route.name} location={location} path={path + route.path} {...route} />)}</ul>}
|
||||
</li>
|
||||
)
|
||||
|
||||
export default NavigationItem
|
||||
10
src/components/Remap.jsx
Normal file
10
src/components/Remap.jsx
Normal file
@ -0,0 +1,10 @@
|
||||
import React from 'react'
|
||||
import RemapForm from './RemapForm'
|
||||
|
||||
export default () => (
|
||||
<div className='remap page'>
|
||||
<h1>Remap Stack Trace</h1>
|
||||
|
||||
<RemapForm />
|
||||
</div>
|
||||
)
|
||||
159
src/components/RemapForm.jsx
Normal file
159
src/components/RemapForm.jsx
Normal file
@ -0,0 +1,159 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { bindAll } from 'lowline'
|
||||
|
||||
const split = /\n?(?= at )/
|
||||
|
||||
const preference = [ 'irw', 'mobile', 'nwp', 'kiosk', 'm2' ]
|
||||
|
||||
export default class RemapForm extends React.Component {
|
||||
constructor () {
|
||||
super()
|
||||
|
||||
this.form = React.createRef()
|
||||
|
||||
bindAll(this, [ 'onSubmit', 'selectApplication', 'selectUiPlatform' ])
|
||||
|
||||
this.state = {
|
||||
error: false,
|
||||
success: false,
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
fetch('/api/source-maps')
|
||||
.then((res) => res.json())
|
||||
.then((sourceMaps) => {
|
||||
this.setState({ sourceMaps })
|
||||
|
||||
this.applications = {};
|
||||
|
||||
sourceMaps.forEach((sourceMap) => {
|
||||
if (!this.applications[sourceMap.application]) {
|
||||
this.applications[sourceMap.application] = {};
|
||||
}
|
||||
|
||||
if (!this.applications[sourceMap.application][sourceMap.uiPlatform]) {
|
||||
this.applications[sourceMap.application][sourceMap.uiPlatform] = [];
|
||||
}
|
||||
|
||||
this.applications[sourceMap.application][sourceMap.uiPlatform].push(sourceMap.version)
|
||||
|
||||
})
|
||||
|
||||
const applications = Object.keys(this.applications);
|
||||
|
||||
this.setState({
|
||||
applications,
|
||||
})
|
||||
|
||||
this.selectApplication(applications[0])
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
onSubmit (e) {
|
||||
e.preventDefault()
|
||||
|
||||
const data = {}
|
||||
|
||||
for (let el of this.form.current.elements) {
|
||||
if (el.name) {
|
||||
data[el.name] = el.value
|
||||
}
|
||||
}
|
||||
|
||||
fetch('/api/remap', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
accepts: 'application/json',
|
||||
'content-type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((res) => {
|
||||
this.setState({
|
||||
success: true,
|
||||
error: false,
|
||||
original: data.stack.split(split).join('\n'),
|
||||
mapped: res.join('\n'),
|
||||
})
|
||||
})
|
||||
.catch((res) => {
|
||||
this.setState({
|
||||
success: false,
|
||||
error: true,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
selectApplication (value) {
|
||||
if (value.target && value.target.value) {
|
||||
value = value.target.value
|
||||
}
|
||||
|
||||
// const uiPlatforms = Object.keys(this.applications[value])
|
||||
|
||||
const application = this.applications[value]
|
||||
this.setState({
|
||||
application,
|
||||
// uiPlatforms,
|
||||
})
|
||||
|
||||
this.selectUiPlatform(Object.keys(application)[0])
|
||||
}
|
||||
|
||||
selectUiPlatform (value) {
|
||||
if (value.target && value.target.value) {
|
||||
value = value.target.value
|
||||
}
|
||||
|
||||
console.log(this.state.application)
|
||||
|
||||
this.setState({
|
||||
versions: this.state.application[value]
|
||||
})
|
||||
}
|
||||
|
||||
render () {
|
||||
const { applications, application, uiPlatform, uiPlatforms, versions, error, success, original, mapped } = this.state
|
||||
|
||||
return (
|
||||
<div className="remap-form">
|
||||
<form ref={this.form} action='/api/remap' method='POST' onSubmit={this.onSubmit}>
|
||||
<div className='row'>
|
||||
<label>Application</label>
|
||||
<select name='application' onChange={this.selectApplication}>
|
||||
{applications && applications.map((application) => <option key={application} value={application}>{application}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
<div className='row'>
|
||||
<label>UI Platform</label>
|
||||
<select name='ui_platform' onChange={this.selectUiPlatform}>
|
||||
{application && Object.keys(application).map((ui) => <option key={ui} value={ui}>{ui}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
<div className='row'>
|
||||
<label>Version</label>
|
||||
<select name='version'>
|
||||
{versions && versions.map((version) => <option key={version} value={version}>{version}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
<div className='row'>
|
||||
<label>Stack</label>
|
||||
<textarea name='stack' />
|
||||
</div>
|
||||
<div>
|
||||
<button type='submit'>Apply</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div className='output'>
|
||||
{original && <div class='output-original'><h2>Original</h2><pre>{original}</pre></div>}
|
||||
{mapped && <div class='output-mapped'><h2>Mapped</h2><pre>{mapped}</pre></div>}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
9
src/components/Start.jsx
Normal file
9
src/components/Start.jsx
Normal file
@ -0,0 +1,9 @@
|
||||
import React from 'react'
|
||||
|
||||
export default () => (
|
||||
<div className='start'>
|
||||
<h1>Welcome</h1>
|
||||
|
||||
<p>To the most legit website in the world. Besides mapping stack traces you can also buy totally ok drugs.</p>
|
||||
</div>
|
||||
)
|
||||
26
src/components/Upload.jsx
Normal file
26
src/components/Upload.jsx
Normal file
@ -0,0 +1,26 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import UploadForm from './UploadForm'
|
||||
|
||||
const Upload = ({ sourceMaps }) => (
|
||||
<div className='upload page'>
|
||||
<h2>Upload a source map</h2>
|
||||
|
||||
<div className='content'>
|
||||
<UploadForm />
|
||||
|
||||
<div className='source-map-list'>
|
||||
<ul>
|
||||
{sourceMaps && sourceMaps.map((sourceMap) => <li>{sourceMap.application} {sourceMap.version} {sourceMap.uiPlatform}</li>)}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
Upload.propTypes = {
|
||||
sourceMaps: PropTypes.array,
|
||||
}
|
||||
|
||||
export default Upload
|
||||
80
src/components/UploadForm.jsx
Normal file
80
src/components/UploadForm.jsx
Normal file
@ -0,0 +1,80 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
export default class UploadForm extends React.Component {
|
||||
constructor () {
|
||||
super()
|
||||
|
||||
this.form = React.createRef()
|
||||
|
||||
this.onSubmit = this.onSubmit.bind(this)
|
||||
|
||||
this.state = {
|
||||
error: false,
|
||||
success: false,
|
||||
}
|
||||
}
|
||||
|
||||
onSubmit (e) {
|
||||
e.preventDefault()
|
||||
|
||||
const data = new FormData(this.form.current)
|
||||
|
||||
fetch('/api/source-maps', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
accepts: 'application/json',
|
||||
},
|
||||
body: data,
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((res) => {
|
||||
this.setState({
|
||||
success: true,
|
||||
error: false,
|
||||
})
|
||||
})
|
||||
.catch((res) => {
|
||||
this.setState({
|
||||
success: false,
|
||||
error: true,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
render () {
|
||||
// const { error, success } = this.state
|
||||
const error = true
|
||||
const success = true
|
||||
|
||||
|
||||
return (
|
||||
<form ref={this.form} action='/api/source-maps' method='POST' encType='multipart/form-data' onSubmit={this.onSubmit}>
|
||||
{success && <div className='success message'>Source Map Uploaded</div>}
|
||||
{error && <div className='error message'>Error Uploading Source Map</div>}
|
||||
<div>
|
||||
<label htmlFor='application'>Application</label>
|
||||
<input type='text' name='application' placeholder='Application' defaultValue='algot' />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor='application'>UI</label>
|
||||
<input type='text' name='uiPlatform' placeholder='UI' />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor='application'>Version</label>
|
||||
<input type='text' name='version' placeholder='Version' defaultValue='4.3.0' />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<input type='file' name='map' required />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button type='submit'>Upload</button>
|
||||
</div>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
}
|
||||
35
src/index.jsx
Normal file
35
src/index.jsx
Normal file
@ -0,0 +1,35 @@
|
||||
import './styles/main.css'
|
||||
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import { BrowserRouter as Router, Route } from 'react-router-dom'
|
||||
|
||||
import Layout from './components/Layout'
|
||||
import routes from './routes'
|
||||
import { store } from './util/history'
|
||||
|
||||
ReactDOM.render((
|
||||
<Router>
|
||||
<Layout routes={routes}>
|
||||
{routes.map((route, i) => <Route key={i} exact path={route.path} component={route.component} />)}
|
||||
</Layout>
|
||||
</Router>
|
||||
), document.getElementById('app'))
|
||||
|
||||
let timeout
|
||||
|
||||
function remember (attrs) {
|
||||
console.log('should be storing')
|
||||
|
||||
store({
|
||||
scrollTop: window.pageYOffset,
|
||||
})
|
||||
}
|
||||
|
||||
window.addEventListener('scroll', () => {
|
||||
if (timeout) {
|
||||
clearTimeout(timeout)
|
||||
}
|
||||
|
||||
timeout = setTimeout(remember, 200)
|
||||
}, { passive: false })
|
||||
6
src/module.mjs
Normal file
6
src/module.mjs
Normal file
@ -0,0 +1,6 @@
|
||||
console.log('what up again. this time some thing up.')
|
||||
|
||||
export const aPoo = 'smelly'
|
||||
export const aSmellierPoo = 'super smelly'
|
||||
|
||||
export default 'you just missed a smelly poo'
|
||||
34
src/remap.mjs
Normal file
34
src/remap.mjs
Normal file
@ -0,0 +1,34 @@
|
||||
import mod from './module'
|
||||
|
||||
const form = document.querySelector('form')
|
||||
const pre = document.querySelector('.remapped-stack')
|
||||
|
||||
console.log(mod)
|
||||
|
||||
form.addEventListener('submit', (e) => {
|
||||
e.preventDefault()
|
||||
|
||||
const obj = {}
|
||||
|
||||
for (let el of e.target.elements) {
|
||||
if (el.name) {
|
||||
obj[el.name] = el.value
|
||||
}
|
||||
}
|
||||
|
||||
console.log(obj)
|
||||
|
||||
fetch('/api/remap', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
accepts: 'application/json',
|
||||
'content-type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(obj),
|
||||
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((res) => {
|
||||
pre.textContent = res.join('\n')
|
||||
})
|
||||
})
|
||||
20
src/routes.js
Normal file
20
src/routes.js
Normal file
@ -0,0 +1,20 @@
|
||||
import Start from './components/Start'
|
||||
import Upload from './components/Upload'
|
||||
import Remap from './components/Remap'
|
||||
|
||||
export default [ {
|
||||
path: '/',
|
||||
component: Start,
|
||||
name: 'start',
|
||||
title: 'Start',
|
||||
}, {
|
||||
path: '/upload',
|
||||
component: Upload,
|
||||
name: 'upload',
|
||||
title: 'Upload',
|
||||
}, {
|
||||
path: '/remap',
|
||||
component: Remap,
|
||||
name: 'remap',
|
||||
title: 'Remap',
|
||||
} ]
|
||||
30
src/styles/content/remap.css
Normal file
30
src/styles/content/remap.css
Normal file
@ -0,0 +1,30 @@
|
||||
.remap.page {
|
||||
.remap-form {
|
||||
display: flex;
|
||||
|
||||
> form {
|
||||
flex: 0 0 500px;
|
||||
max-width: 500px;
|
||||
margin-right: $global-gutter;
|
||||
}
|
||||
|
||||
> div {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
}
|
||||
|
||||
form {
|
||||
> div {
|
||||
margin: 4px;
|
||||
|
||||
> label {
|
||||
max-width: 120px;
|
||||
}
|
||||
}
|
||||
|
||||
textarea {
|
||||
min-height: 340px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
0
src/styles/content/upload.css
Normal file
0
src/styles/content/upload.css
Normal file
66
src/styles/layout.css
Normal file
66
src/styles/layout.css
Normal file
@ -0,0 +1,66 @@
|
||||
@import './mixins';
|
||||
|
||||
#app {
|
||||
> .container {
|
||||
@include .container($content-max-width, $global-gutter);
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
> header {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
> h1 {
|
||||
> a {
|
||||
color: black;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
nav {
|
||||
> button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
> ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
display: flex;
|
||||
|
||||
> li {
|
||||
display: block;
|
||||
margin: 5px;
|
||||
|
||||
&:first-child {
|
||||
margin-left: 0px;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0px;
|
||||
}
|
||||
|
||||
> a {
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
font-weight: bold;
|
||||
border: 5px solid black;
|
||||
padding: $button-padding;
|
||||
color: black;
|
||||
text-decoration: none;
|
||||
transition: all 0.5s;
|
||||
|
||||
&:hover {
|
||||
background: black;
|
||||
color:white;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
5
src/styles/main.css
Normal file
5
src/styles/main.css
Normal file
@ -0,0 +1,5 @@
|
||||
@import './variables';
|
||||
@import './layout';
|
||||
@import './styles';
|
||||
@import './content/upload';
|
||||
@import './content/remap';
|
||||
57
src/styles/mixins.css
Normal file
57
src/styles/mixins.css
Normal file
@ -0,0 +1,57 @@
|
||||
$content-max-width: 1000px !default;
|
||||
$global-gutter: 30px !default;
|
||||
|
||||
@mixin .container($max-width: $content-max-width, $gutter: $global-gutter) {
|
||||
width: calc(100% - $gutter);
|
||||
max-width: $max-width;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
@mixin .grid($direction: row) {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
flex-direction: $direction;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
@mixin .cell($count: 5, $gutter: $global-gutter) {
|
||||
flex: 0 0 auto;
|
||||
width: calc(100% / $count ~"-" (($count - 1) * $gutter / $count));
|
||||
margin-right: $gutter;
|
||||
|
||||
&:nth-child($(count)n) {
|
||||
margin-right: 0;
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin .wipe {
|
||||
/*Reset's every elements apperance*/
|
||||
background: none repeat scroll 0 0 transparent;
|
||||
border: medium none;
|
||||
border-spacing: 0;
|
||||
border-radius: 0;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
font-weight: inherit;
|
||||
line-height: inherit;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
text-align: left;
|
||||
text-decoration: none;
|
||||
text-transform: inherit;
|
||||
text-indent: 0;
|
||||
}
|
||||
|
||||
@mixin .wipe-button {
|
||||
@include .wipe;
|
||||
|
||||
&:focus {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
&::-moz-focus-inner {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
59
src/styles/styles.css
Normal file
59
src/styles/styles.css
Normal file
@ -0,0 +1,59 @@
|
||||
@import './mixins';
|
||||
|
||||
*, *:before, *:after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: #ffcc33;
|
||||
}
|
||||
|
||||
.grid {
|
||||
@include .grid();
|
||||
}
|
||||
|
||||
button {
|
||||
@include .wipe-button;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
border: 5px solid black;
|
||||
padding: $button-padding;
|
||||
color: white;
|
||||
background: black;
|
||||
font-weight: bold;
|
||||
transition: all 0.5s;
|
||||
|
||||
&:hover {
|
||||
background: white;
|
||||
color: black;
|
||||
}
|
||||
}
|
||||
|
||||
input, textarea {
|
||||
margin: 0;
|
||||
padding: 8px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.message {
|
||||
border-left: 5px solid transparent;
|
||||
padding: 15px 20px;
|
||||
|
||||
&.error {
|
||||
$color: red;
|
||||
|
||||
color: color($color shade(20%));
|
||||
border-left-color: color($color shade(20%));
|
||||
background: color($color tint(90%));
|
||||
}
|
||||
|
||||
&.success {
|
||||
$color: green;
|
||||
|
||||
color: color($color shade(20%));
|
||||
border-left-color: color($color shade(20%));
|
||||
background: color($color tint(90%));
|
||||
}
|
||||
}
|
||||
|
||||
4
src/styles/variables.css
Normal file
4
src/styles/variables.css
Normal file
@ -0,0 +1,4 @@
|
||||
$content-max-width: 1280px;
|
||||
$global-gutter: 30px;
|
||||
$button-padding: 10px 20px;
|
||||
|
||||
30
src/upload.mjs
Normal file
30
src/upload.mjs
Normal file
@ -0,0 +1,30 @@
|
||||
const form = document.querySelector('form')
|
||||
const pre = document.querySelector('.remapped-stack')
|
||||
|
||||
form.addEventListener('submit', (e) => {
|
||||
e.preventDefault()
|
||||
|
||||
console.log(e.target)
|
||||
const data = new FormData(form)
|
||||
console.log('should be appending')
|
||||
data.append('testing', 'what')
|
||||
data.set('setting', 'what')
|
||||
|
||||
for (const p of data) {
|
||||
console.log(p)
|
||||
}
|
||||
// console.log(data.entries())
|
||||
// console.log(data.getAll())
|
||||
|
||||
fetch('/api/upload', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
accepts: 'application/json',
|
||||
},
|
||||
body: data,
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((res) => {
|
||||
pre.textContent = res.join('\n')
|
||||
})
|
||||
})
|
||||
15
src/util/history.js
Normal file
15
src/util/history.js
Normal file
@ -0,0 +1,15 @@
|
||||
export function getCurrentUrl () {
|
||||
const url = typeof location !== 'undefined' ? location : EMPTY
|
||||
|
||||
return `${url.pathname || ''}${url.search || ''}`
|
||||
}
|
||||
|
||||
export function setUrl (url, state = null, type = 'push') {
|
||||
if (typeof history !== 'undefined' && history[`${type}State`]) {
|
||||
history[`${type}State`](state, null, url)
|
||||
}
|
||||
}
|
||||
|
||||
export function store (data) {
|
||||
setUrl(getCurrentUrl(), Object.assign({}, window.history.state, data), 'replace')
|
||||
}
|
||||
49
webpack.config.js
Normal file
49
webpack.config.js
Normal file
@ -0,0 +1,49 @@
|
||||
'use strict'
|
||||
|
||||
const path = require('path')
|
||||
// const webpack = require('webpack')
|
||||
const ExtractTextPlugin = require('extract-text-webpack-plugin')
|
||||
|
||||
const outputDir = path.resolve(__dirname, 'public')
|
||||
|
||||
module.exports = {
|
||||
mode: 'development',
|
||||
|
||||
entry: './src/index.jsx',
|
||||
|
||||
resolve: {
|
||||
extensions: ['.js', '.jsx', '.mjs', '.json'],
|
||||
},
|
||||
|
||||
output: {
|
||||
path: outputDir,
|
||||
|
||||
filename: 'js/bundle.js',
|
||||
},
|
||||
|
||||
module: {
|
||||
rules: [{
|
||||
test: /\.css$/,
|
||||
use: ExtractTextPlugin.extract({
|
||||
fallback: 'style-loader',
|
||||
use: [ {
|
||||
loader: 'css-loader',
|
||||
options: { importLoaders: 1 },
|
||||
}, 'postcss-loader' ],
|
||||
}),
|
||||
}, {
|
||||
test: /\.(mjs|js|jsx)$/,
|
||||
exclude: /node_modules/,
|
||||
use: {
|
||||
loader: 'babel-loader',
|
||||
},
|
||||
}],
|
||||
},
|
||||
|
||||
plugins: [
|
||||
new ExtractTextPlugin({
|
||||
filename: 'css/bundle.css',
|
||||
allChunks: true,
|
||||
}),
|
||||
],
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user