├── .babelrc
├── .eslintignore
├── .eslintrc.json
├── .github
└── workflows
│ └── nodejs.yml
├── .gitignore
├── LICENSE
├── README.md
├── api
├── get.js
├── keys.js
├── scan.js
├── server.js
└── stats.js
├── app.js
├── components
├── loading.jsx
├── map.jsx
├── menu.jsx
├── page.jsx
├── panel.jsx
├── properties.jsx
└── tabs.jsx
├── index.jsx
├── lib
├── http.js
└── routie.js
├── package-lock.json
├── package.json
├── pages
├── key.jsx
├── keys.jsx
├── object.jsx
└── server.jsx
└── wwwroot
├── css
├── style.css
├── style.css.map
├── style.min.css
└── style.min.css.map
├── index.html
├── index.min.js
└── vendors
├── @coreui
├── coreui
│ └── js
│ │ ├── coreui.min.js
│ │ └── coreui.min.js.map
└── icons
│ ├── css
│ ├── coreui-icons.min.css
│ └── coreui-icons.min.css.map
│ └── fonts
│ ├── CoreUI-Icons-Linear-Free.eot
│ ├── CoreUI-Icons-Linear-Free.svg
│ ├── CoreUI-Icons-Linear-Free.ttf
│ └── CoreUI-Icons-Linear-Free.woff
├── font-awesome
├── css
│ └── font-awesome.min.css
└── fonts
│ ├── fontawesome-webfont.eot
│ ├── fontawesome-webfont.svg
│ ├── fontawesome-webfont.ttf
│ ├── fontawesome-webfont.woff
│ └── fontawesome-webfont.woff2
└── simple-line-icons
├── css
└── simple-line-icons.css
└── fonts
├── Simple-Line-Icons.eot
├── Simple-Line-Icons.svg
├── Simple-Line-Icons.ttf
├── Simple-Line-Icons.woff
└── Simple-Line-Icons.woff2
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["@babel/preset-react", "@babel/preset-env"]
3 | }
4 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | css/*
2 | vs/*
3 | vendors/*
4 | *.min.js
5 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es6": true
5 | },
6 | "extends": "eslint:recommended",
7 | "parserOptions": {
8 | "ecmaVersion":8,
9 | "ecmaFeatures": {
10 | "experimentalObjectRestSpread": true,
11 | "jsx": true
12 | }
13 | },
14 | "plugins": [
15 | "react"
16 | ],
17 | "globals": {
18 | "module": true,
19 | "require": true,
20 | "L": true,
21 | "settings": true,
22 | "define": true,
23 | "Buffer": true,
24 | "google" : true,
25 | "describe": true,
26 | "it":true,
27 | "$":true
28 | },
29 | "rules": {
30 | "indent": "off",
31 | "linebreak-style": "off",
32 | "quotes": "off",
33 | "semi": "off",
34 | "no-mixed-spaces-and-tabs" : "off",
35 | "no-unused-vars" : "off",
36 | "no-redeclare": "off",
37 | "no-empty": "off",
38 | "no-console":"off",
39 | "no-useless-escape":"off"
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/.github/workflows/nodejs.yml:
--------------------------------------------------------------------------------
1 | name: Node CI
2 |
3 | on: [push]
4 |
5 | jobs:
6 | build:
7 |
8 | runs-on: ubuntu-latest
9 |
10 | strategy:
11 | matrix:
12 | node-version: [8.x, 10.x, 12.x]
13 |
14 | steps:
15 | - uses: actions/checkout@v1
16 | - name: Use Node.js ${{ matrix.node-version }}
17 | uses: actions/setup-node@v1
18 | with:
19 | node-version: ${{ matrix.node-version }}
20 | - name: npm install, build, and test
21 | run: |
22 | npm ci
23 | npm run build
24 | env:
25 | CI: true
26 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Richard Astbury
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # portal38
4 | :bar_chart: An admin portal for tile38
5 |
6 | # License
7 |
8 | MIT
9 |
--------------------------------------------------------------------------------
/api/get.js:
--------------------------------------------------------------------------------
1 | const express = require('express')
2 | const router = express.Router()
3 |
4 | router.get('/api/get/:key/:id', async (req, res, next) => {
5 | try {
6 | res.json(await req.t38.get(req.params.key, req.params.id))
7 | } catch (err) {
8 | console.log(err)
9 | next(err)
10 | }
11 | })
12 |
13 | module.exports = router
--------------------------------------------------------------------------------
/api/keys.js:
--------------------------------------------------------------------------------
1 | const express = require('express')
2 | const router = express.Router()
3 |
4 | router.get('/api/keys', async (req, res) => {
5 | try {
6 | res.json(await req.t38.keys('*'))
7 | } catch (err) {
8 | console.log(err)
9 | next(err)
10 | }
11 | })
12 |
13 | module.exports = router
--------------------------------------------------------------------------------
/api/scan.js:
--------------------------------------------------------------------------------
1 | const express = require('express')
2 | const router = express.Router()
3 |
4 | router.get('/api/scan/:key', async (req, res) => {
5 | try {
6 | const query = req.t38
7 | .scanQuery(req.params.key)
8 | .limit(parseInt(req.query.limit || '100'))
9 |
10 | if (req.query.cursor) {
11 | query.cursor(parseInt(req.query.cursor))
12 | }
13 | res.json(await query.execute())
14 | } catch (err) {
15 | console.log(err)
16 | next(err)
17 | }
18 | })
19 |
20 | module.exports = router
21 |
--------------------------------------------------------------------------------
/api/server.js:
--------------------------------------------------------------------------------
1 | const express = require('express')
2 | const router = express.Router()
3 |
4 | router.get('/api/server', async (req, res) => {
5 | try {
6 | res.json(await req.t38.server())
7 | } catch (err) {
8 | console.log(err)
9 | next(err)
10 | }
11 | })
12 |
13 | module.exports = router
--------------------------------------------------------------------------------
/api/stats.js:
--------------------------------------------------------------------------------
1 | const express = require('express')
2 | const router = express.Router()
3 |
4 | router.get('/api/stats/:key', async (req, res) => {
5 | try {
6 | res.json(await req.t38.stats(req.params.key))
7 | } catch (err) {
8 | console.log(err)
9 | next(err)
10 | }
11 | })
12 |
13 | module.exports = router
--------------------------------------------------------------------------------
/app.js:
--------------------------------------------------------------------------------
1 | const express = require('express')
2 | const app = express()
3 | const logger = require('morgan')
4 | const Tile38 = require('tile38')
5 | const fs = require('fs')
6 |
7 | const [_, __, t38host, t38port, t38username, t38password] = process.argv
8 |
9 | const t38 = new Tile38({
10 | host: t38host || '127.0.0.1',
11 | port: parseInt(t38port || '9851'),
12 | debug: false
13 | })
14 |
15 | console.log('testing connection to tile 38')
16 |
17 | t38
18 | .ping()
19 | .then(() => {
20 | console.log('connected to tile38')
21 | startExpress()
22 | })
23 | .catch(err => console.log(`error ${err}`))
24 |
25 | app.use((req, res, next) => {
26 | req.t38 = t38
27 | next()
28 | })
29 |
30 | app.use(express.static('wwwroot'))
31 | app.use(logger('dev'))
32 |
33 | // require the whole ./api and ./routes directories
34 | const requireDir = dir => {
35 | fs.readdirSync(dir)
36 | .forEach(file => {
37 | console.log(`using ${dir}/${file}`)
38 | app.use('/', require(`./${dir}/${file}`))
39 | })
40 | }
41 |
42 | requireDir('api')
43 |
44 | function startExpress() {
45 | const port = process.env.PORT || 8080
46 | app.listen(port)
47 | console.log(`listening on port ${port}`)
48 | }
49 |
--------------------------------------------------------------------------------
/components/loading.jsx:
--------------------------------------------------------------------------------
1 | const React = require('react')
2 |
3 | const Loading = () => {
4 | return Loading...
5 | }
6 |
7 | module.exports = Loading
--------------------------------------------------------------------------------
/components/map.jsx:
--------------------------------------------------------------------------------
1 | const React = require('react')
2 | import Map from 'ol/Map'
3 | import View from 'ol/View'
4 | import XYZ from 'ol/source/XYZ'
5 | import GeoJSON from 'ol/format/GeoJSON'
6 | import { Tile as TileLayer, Vector as VectorLayer } from 'ol/layer.js'
7 | import { Vector as VectorSource } from 'ol/source.js'
8 | import { Circle as CircleStyle, Fill, Stroke, Style } from 'ol/style.js'
9 |
10 | const image = new CircleStyle({
11 | radius: 5,
12 | fill: null,
13 | stroke: new Stroke({ color: 'red', width: 1 })
14 | })
15 |
16 | const MapComponent = class extends React.PureComponent {
17 | styleFunction(feature) {
18 | return new Style({
19 | stroke: new Stroke({
20 | color: 'red',
21 | width: 3
22 | }),
23 | fill: new Fill({
24 | color: 'rgba(255,0,0,0.2)'
25 | }),
26 | image
27 | })
28 | }
29 | componentDidMount() {
30 | const vectorSource = new VectorSource({
31 | features: new GeoJSON().readFeatures(this.props.geoJson, {
32 | dataProjection: 'EPSG:4326',
33 | featureProjection: 'EPSG:3857'
34 | })
35 | })
36 |
37 | new Map({
38 | target: this.refs.map,
39 | layers: [
40 | new TileLayer({
41 | source: new XYZ({
42 | url: 'http://{a-c}.tiles.wmflabs.org/bw-mapnik/{z}/{x}/{y}.png'// 'https://{a-c}.tile.openstreetmap.org/{z}/{x}/{y}.png'
43 | })
44 | }),
45 | new VectorLayer({
46 | source: vectorSource,
47 | style: this.styleFunction
48 | })
49 | ],
50 | view: new View({
51 | center: [0, 0],
52 | zoom: 2
53 | })
54 | })
55 | }
56 | render() {
57 | return (
58 |
62 | )
63 | }
64 | }
65 |
66 | module.exports = MapComponent
67 |
--------------------------------------------------------------------------------
/components/menu.jsx:
--------------------------------------------------------------------------------
1 | const React = require('react')
2 |
3 | const menuItems = [
4 | {
5 | href: '#/server',
6 | title: 'Server',
7 | icon: 'icon-speedometer'
8 | },
9 | {
10 | href: '#/keys',
11 | title: 'Keys',
12 | icon: 'icon-layers'
13 | }
14 | ]
15 |
16 | const MenuItem = props => {
17 | return (
18 |
19 |
20 | {props.title}
21 |
22 |
23 | )
24 | }
25 |
26 | const Menu = props => {
27 | return (
28 |
29 | {menuItems.map((x, index) => {
30 | const active = props.activeMenuItem === x.href
31 | return
32 | })}
33 |
34 | )
35 | }
36 |
37 | module.exports = Menu
--------------------------------------------------------------------------------
/components/page.jsx:
--------------------------------------------------------------------------------
1 | const React = require('react')
2 | const Fragment = React.Fragment
3 | const Menu = require('./menu.jsx')
4 |
5 | const Page = props => {
6 | return (
7 |
8 |
13 |
14 |
15 |
16 |
17 | {props.children}
18 |
19 |
20 |
21 |
22 | )
23 | }
24 |
25 | module.exports = Page
--------------------------------------------------------------------------------
/components/panel.jsx:
--------------------------------------------------------------------------------
1 | const React = require('react')
2 |
3 | module.exports = props => {
4 | if (props.children.length) {
5 | var body = props.children[0]
6 | var footer = {props.children[1]}
7 | } else {
8 | var body = props.children
9 | var footer = null
10 | }
11 |
12 | return (
13 |
14 |
{props.title}
15 |
16 | {body}
17 |
18 | {footer}
19 |
20 | )
21 | }
22 |
--------------------------------------------------------------------------------
/components/properties.jsx:
--------------------------------------------------------------------------------
1 | const React = require('react')
2 |
3 | const PropertyItem = props => {
4 | const { keyName, value } = props
5 | return (
6 |
7 | {keyName} |
8 | {value} |
9 |
10 | )
11 | }
12 |
13 | const Properties = props => {
14 | return (
15 |
16 |
17 | {Object.keys(props.value).map(key => (
18 |
19 | ))}
20 |
21 |
22 | )
23 | }
24 |
25 | module.exports = Properties
26 |
--------------------------------------------------------------------------------
/components/tabs.jsx:
--------------------------------------------------------------------------------
1 | const React = require('react')
2 |
3 | const Tab = props => {
4 | return (
5 |
6 |
11 | {props.title}
12 |
13 |
14 | )
15 | }
16 |
17 | const Tabs = class extends React.Component {
18 | constructor(props) {
19 | super(props)
20 | this.state = { active: 0 }
21 | this.handleClick = this.handleClick.bind(this)
22 | }
23 |
24 | handleClick(index) {
25 | this.setState({ active: index })
26 | if (this.props.onClick) this.props.onClick(this.props.tabs[index])
27 | }
28 |
29 | render() {
30 | return (
31 |
32 | {this.props.tabs.map((tab, index) => (
33 |
40 | ))}
41 |
42 | )
43 | }
44 | }
45 |
46 | module.exports = Tabs
47 |
--------------------------------------------------------------------------------
/index.jsx:
--------------------------------------------------------------------------------
1 | const React = require('react')
2 | const ReactDOM = require('react-dom')
3 | const Page = require('./components/page.jsx')
4 | const Panel = require('./components/panel.jsx')
5 | const routie = require('./lib/routie')
6 | const Server = require('./pages/server.jsx')
7 | const Keys = require('./pages/keys.jsx')
8 | const Key = require('./pages/key.jsx')
9 | const Object = require('./pages/object.jsx')
10 |
11 | const contentElement = document.getElementById('content')
12 | function render(jsx) {
13 | ReactDOM.render(jsx, contentElement)
14 | }
15 |
16 | render(
17 |
18 |
19 | Hello World
20 |
21 |
22 | )
23 |
24 | routie('/server', () => {
25 | render(
26 |
27 |
28 |
29 | )
30 | })
31 |
32 | routie('/keys', () => {
33 | render(
34 |
35 |
36 |
37 | )
38 | })
39 |
40 | routie('/keys/:key', key => {
41 | render(
42 |
43 |
44 |
45 | )
46 | })
47 |
48 | routie('/object/:key/:id', (key, id) => {
49 | render(
50 |
51 |
52 |
53 | )
54 | })
55 |
56 | routie.reload()
57 |
--------------------------------------------------------------------------------
/lib/http.js:
--------------------------------------------------------------------------------
1 | function makeRequest(method, uri, body, cb) {
2 | var xhr = new XMLHttpRequest()
3 | xhr.open(method, uri, true)
4 | xhr.onreadystatechange = function() {
5 | if (xhr.readyState !== 4) return
6 | if (xhr.status < 400 && xhr.status > 0)
7 | return cb(null, JSON.parse(xhr.responseText || '{}'))
8 | var errorMessage =
9 | 'Error connecting to server. Status code: ' +
10 | (xhr.status || 'NO_CONNECTION')
11 | errorHandlers.forEach(x => x(errorMessage))
12 | }
13 | xhr.setRequestHeader('Content-Type', 'application/json')
14 | xhr.setRequestHeader('Accept', 'application/json')
15 | xhr.send(body)
16 | }
17 |
18 | module.exports.get = url => {
19 | return new Promise((resolve, reject) => {
20 | makeRequest('GET', url, null, (err, body) => {
21 | if (err) return reject(err)
22 | resolve(body)
23 | })
24 | })
25 | }
--------------------------------------------------------------------------------
/lib/routie.js:
--------------------------------------------------------------------------------
1 | (function(root, factory) {
2 | if (typeof exports === 'object') {
3 | module.exports = factory(window)
4 | } else if (typeof define === 'function' && define.amd) {
5 | define([], function() {
6 | return (root.routie = factory(window))
7 | })
8 | } else {
9 | root.routie = factory(window)
10 | }
11 | })(this, function(w) {
12 | var routes = []
13 | var map = {}
14 | var reference = 'routie'
15 | var oldReference = w[reference]
16 |
17 | var Route = function(path, name) {
18 | this.name = name
19 | this.path = path
20 | this.keys = []
21 | this.fns = []
22 | this.params = {}
23 | this.regex = pathToRegexp(this.path, this.keys, false, false)
24 | }
25 |
26 | Route.prototype.addHandler = function(fn) {
27 | this.fns.push(fn)
28 | }
29 |
30 | Route.prototype.removeHandler = function(fn) {
31 | for (var i = 0, c = this.fns.length; i < c; i++) {
32 | var f = this.fns[i]
33 | if (fn == f) {
34 | this.fns.splice(i, 1)
35 | return
36 | }
37 | }
38 | }
39 |
40 | Route.prototype.run = function(params) {
41 | for (var i = 0, c = this.fns.length; i < c; i++) {
42 | this.fns[i].apply(this, params)
43 | }
44 | }
45 |
46 | Route.prototype.match = function(path, params) {
47 | var m = this.regex.exec(path)
48 |
49 | if (!m) return false
50 |
51 | for (var i = 1, len = m.length; i < len; ++i) {
52 | var key = this.keys[i - 1]
53 |
54 | var val = 'string' == typeof m[i] ? decodeURIComponent(m[i]) : m[i]
55 |
56 | if (key) {
57 | this.params[key.name] = val
58 | }
59 | params.push(val)
60 | }
61 |
62 | return true
63 | }
64 |
65 | Route.prototype.toURL = function(params) {
66 | var path = this.path
67 | for (var param in params) {
68 | path = path.replace('/:' + param, '/' + params[param])
69 | }
70 | path = path.replace(/\/:.*\?/g, '/').replace(/\?/g, '')
71 | if (path.indexOf(':') != -1) {
72 | throw new Error('missing parameters for url: ' + path)
73 | }
74 | return path
75 | }
76 |
77 | var pathToRegexp = function(path, keys, sensitive, strict) {
78 | if (path instanceof RegExp) return path
79 | if (path instanceof Array) path = '(' + path.join('|') + ')'
80 | path = path
81 | .concat(strict ? '' : '/?')
82 | .replace(/\/\(/g, '(?:/')
83 | .replace(/\+/g, '__plus__')
84 | .replace(/(\/)?(\.)?:(\w+)(?:(\(.*?\)))?(\?)?/g, function(
85 | _,
86 | slash,
87 | format,
88 | key,
89 | capture,
90 | optional
91 | ) {
92 | keys.push({ name: key, optional: !!optional })
93 | slash = slash || ''
94 | return (
95 | '' +
96 | (optional ? '' : slash) +
97 | '(?:' +
98 | (optional ? slash : '') +
99 | (format || '') +
100 | (capture || ((format && '([^/.]+?)') || '([^/]+?)')) +
101 | ')' +
102 | (optional || '')
103 | )
104 | })
105 | .replace(/([\/.])/g, '\\$1')
106 | .replace(/__plus__/g, '(.+)')
107 | .replace(/\*/g, '(.*)')
108 | return new RegExp('^' + path + '$', sensitive ? '' : 'i')
109 | }
110 |
111 | var addHandler = function(path, fn) {
112 | var s = path.split(' ')
113 | var name = s.length == 2 ? s[0] : null
114 | path = s.length == 2 ? s[1] : s[0]
115 |
116 | if (!map[path]) {
117 | map[path] = new Route(path, name)
118 | routes.push(map[path])
119 | }
120 | map[path].addHandler(fn)
121 | }
122 |
123 | var routie = function(path, fn) {
124 | if (typeof fn == 'function') {
125 | addHandler(path, fn)
126 | //routie.reload();
127 | } else if (typeof path == 'object') {
128 | for (var p in path) {
129 | addHandler(p, path[p])
130 | }
131 | //routie.reload();
132 | } else if (typeof fn === 'undefined') {
133 | routie.navigate(path)
134 | }
135 | }
136 |
137 | routie.lookup = function(name, obj) {
138 | for (var i = 0, c = routes.length; i < c; i++) {
139 | var route = routes[i]
140 | if (route.name == name) {
141 | return route.toURL(obj)
142 | }
143 | }
144 | }
145 |
146 | routie.remove = function(path, fn) {
147 | var route = map[path]
148 | if (!route) return
149 | route.removeHandler(fn)
150 | }
151 |
152 | routie.removeAll = function() {
153 | map = {}
154 | routes = []
155 | }
156 |
157 | routie.navigate = function(path, options) {
158 | options = options || {}
159 | var silent = options.silent || false
160 |
161 | if (silent) {
162 | removeListener()
163 | }
164 | setTimeout(function() {
165 | window.location.hash = path
166 |
167 | if (silent) {
168 | setTimeout(function() {
169 | addListener()
170 | }, 1)
171 | }
172 | }, 1)
173 | }
174 |
175 | routie.noConflict = function() {
176 | w[reference] = oldReference
177 | return routie
178 | }
179 |
180 | var getHash = function() {
181 | return window.location.hash.substring(1)
182 | }
183 |
184 | var checkRoute = function(hash, route) {
185 | var params = []
186 | if (route.match(hash, params)) {
187 | route.run(params)
188 | return true
189 | }
190 | return false
191 | }
192 |
193 | var hashChanged = (routie.reload = function() {
194 | var hash = getHash()
195 | for (var i = 0, c = routes.length; i < c; i++) {
196 | var route = routes[i]
197 | if (checkRoute(hash, route)) {
198 | return
199 | }
200 | }
201 | })
202 |
203 | var addListener = function() {
204 | if (w.addEventListener) {
205 | w.addEventListener('hashchange', hashChanged, false)
206 | } else {
207 | w.attachEvent('onhashchange', hashChanged)
208 | }
209 | }
210 |
211 | var removeListener = function() {
212 | if (w.removeEventListener) {
213 | w.removeEventListener('hashchange', hashChanged)
214 | } else {
215 | w.detachEvent('onhashchange', hashChanged)
216 | }
217 | }
218 | addListener()
219 |
220 | return routie
221 | })
222 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "portal38",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "app.js",
6 | "scripts": {
7 | "start": "npm run watch & node app.js",
8 | "example": "browserify -g [ babelify --presets [ \"@babel/preset-env\" ] ] --entry index.jsx > bundle.js",
9 | "watch": "watchify -v -g [ babelify --presets [ \"@babel/preset-env\" ] ] index.jsx -g [ envify --NODE_ENV development ] -o ./wwwroot/index.min.js",
10 | "build": "npm run browserify",
11 | "lint": "eslint . --ext .js --ext .jsx",
12 | "test": "mocha",
13 | "browserify": "browserify -g babelify index.jsx -g [ envify --NODE_ENV production ] -g uglifyify | uglifyjs --compress warnings=false --mangle > ./wwwroot/index.min.js"
14 | },
15 | "author": "Richard Astbury",
16 | "license": "MIT",
17 | "dependencies": {
18 | "express": "^4.18.2",
19 | "morgan": "^1.9.1",
20 | "tile38": "^0.6.6"
21 | },
22 | "devDependencies": {
23 | "@babel/core": "^7.4.0",
24 | "@babel/preset-env": "^7.4.2",
25 | "@babel/preset-react": "^7.0.0",
26 | "babelify": "^9.0.0",
27 | "browserify": "^16.2.3",
28 | "envify": "^4.1.0",
29 | "eslint-plugin-react": "^7.12.4",
30 | "ol": "^5.3.1",
31 | "react": "^16.8.5",
32 | "react-dom": "^16.8.5",
33 | "uglify-js": "^3.5.2",
34 | "uglifyify": "^5.0.2",
35 | "watchify": "^3.11.1"
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/pages/key.jsx:
--------------------------------------------------------------------------------
1 | const React = require('react')
2 | const { Component, Fragment } = React
3 | const Panel = require('../components/panel.jsx')
4 | const Loading = require('../components/loading.jsx')
5 | const http = require('../lib/http')
6 | const Tabs = require('../components/tabs.jsx')
7 | const Properties = require('../components/properties.jsx')
8 | const Map = require('../components/map.jsx')
9 |
10 | const tabs = [
11 | {
12 | icon: 'icon-docs',
13 | title: 'Objects'
14 | },
15 | {
16 | icon: 'icon-map',
17 | title: 'Map'
18 | },
19 | {
20 | icon: 'icon-speedometer',
21 | title: 'Stats'
22 | }
23 | ]
24 |
25 | const [OBJECTS, MAP, STATS] = tabs
26 |
27 | const Key = class extends Component {
28 | constructor(props) {
29 | super(props)
30 | this.state = { loading: true, data: null, activeTab: tabs[0], stats: null }
31 |
32 | this.handleScanResponse = this.handleScanResponse.bind(this)
33 | this.handleStatsResponse = this.handleStatsResponse.bind(this)
34 | this.handleClick = this.handleClick.bind(this)
35 | }
36 |
37 | componentDidMount() {
38 | http.get(`/api/scan/${this.props.keyName}`).then(this.handleScanResponse)
39 | }
40 |
41 | handleScanResponse(data) {
42 | this.setState({ data, loading: false })
43 | }
44 |
45 | handleClick(activeTab) {
46 | this.setState({ activeTab })
47 | if (activeTab === STATS) {
48 | http
49 | .get(`/api/stats/${this.props.keyName}`)
50 | .then(this.handleStatsResponse)
51 | }
52 | }
53 |
54 | handleStatsResponse(data) {
55 | this.setState({ stats: data[0] })
56 | }
57 |
58 | renderItem(item) {
59 | return (
60 |
65 | {item.id}
66 |
67 | )
68 | }
69 |
70 | renderList() {
71 | return (
72 |
73 | {this.state.data.objects.map(x => this.renderItem(x))}
74 |
75 | )
76 | }
77 |
78 | renderMap() {
79 | return (
80 |