Browse Source

Vue single file component implementation

- Using webpack for bundling
develop
Mark van Renswoude 1 year ago
parent
commit
7ae2f49ee8
  1. 3
      .browserslistrc
  2. 1
      .gitignore
  3. 5
      babel.config.js
  4. 24
      devserver.js
  5. 82
      gulpfile.js
  6. 19402
      package-lock.json
  7. 37
      package.json
  8. 108
      src/assets/css.h
  9. 5562
      src/assets/js.h
  10. 2
      src/server/static.cpp
  11. 615
      web/app.js
  12. 1
      web/dist/bundle.css
  13. 1
      web/dist/bundle.js
  14. 201
      web/index.html
  15. 207
      web/lang.js
  16. 17
      web/public/index.html
  17. 723
      web/site.scss
  18. 990
      web/src/App.vue
  19. 147
      web/src/app.js
  20. BIN
      web/src/assets/logo.png
  21. 44
      web/src/components/check.vue
  22. 43
      web/src/components/loadingIndicator.vue
  23. 42
      web/src/components/radio.vue
  24. 56
      web/src/components/range.vue
  25. 102
      web/src/i18n/en.js
  26. 7
      web/src/i18n/index.js
  27. 102
      web/src/i18n/nl.js
  28. 127
      web/src/index.html
  29. 0
      web/src/logo.ai
  30. 0
      web/src/logo.png
  31. 63
      web/src/main.js
  32. 23
      web/src/router/index.js
  33. 109
      web/src/store/index.js
  34. 0
      web/src/variables.scss
  35. 130
      web/src/views/Status.vue
  36. 19
      webpack.build.js
  37. 68
      webpack.config.js
  38. 18
      webpack.dev.js

3
.browserslistrc

@ -0,0 +1,3 @@
> 1%
last 2 versions
not dead

1
.gitignore

@ -1,3 +1,4 @@
node_modules
bin
web/dist
.pio

5
babel.config.js

@ -0,0 +1,5 @@
module.exports = {
presets: [
'@babel/preset-env'
]
}

24
devserver.js

@ -13,7 +13,7 @@ app.use(bodyParser.json());
app.use(express.static('web'));
app.use(express.static('web/dist'));
app.get('/api/status', function(req, res)
app.get('/api/status', (req, res) =>
{
res.send({
systemID: 'dev-server',
@ -23,7 +23,7 @@ app.get('/api/status', function(req, res)
});
});
app.get('/api/connection', function(req, res)
app.get('/api/connection', (req, res) =>
{
res.send({
hostname: 'dev-server',
@ -38,7 +38,7 @@ app.get('/api/connection', function(req, res)
});
});
app.get('/api/connection/status', function(req, res)
app.get('/api/connection/status', (req, res) =>
{
res.send({
"ap": {
@ -53,12 +53,12 @@ app.get('/api/connection/status', function(req, res)
});
});
app.post('/api/connection', function(req, res)
app.post('/api/connection', (req, res) =>
{
res.sendStatus(200);
});
app.post('/api/firmware', function(req, res)
app.post('/api/firmware', (req, res) =>
{
res.sendStatus(200);
});
@ -73,12 +73,12 @@ var system = {
ledCount: 60
};
app.get('/api/system', function(req, res)
app.get('/api/system', (req, res) =>
{
res.send(system);
});
app.post('/api/system', function(req, res)
app.post('/api/system', (req, res) =>
{
var body = req.body;
if (body)
@ -96,12 +96,18 @@ app.post('/api/system', function(req, res)
app.get('/api/stacktrace/get', function(req, res)
app.get('/api/stacktrace/get', (req, res) =>
{
res.send("Nothing to see here, move along!");
});
app.get('/api/stacktrace/delete', function(req, res)
app.get('/api/stacktrace/delete', (req, res) =>
{
res.sendStatus(200);
});
app.get('/api/set/static', (req, res) =>
{
res.sendStatus(200);
});

82
gulpfile.js

@ -19,6 +19,8 @@ const concat = require('gulp-concat');
const print = require('gulp-print');
const path = require('path');
const gzip = require('gulp-gzip');
const webpack = require('webpack')
const webpackConfig = require('./webpack.build.js')
const config = {
@ -31,6 +33,23 @@ const config = {
};
function runWebpack()
{
return new Promise((resolve, reject) =>
{
webpack(webpackConfig, (err, stats) =>
{
if (err)
return reject(err);
if (stats.hasErrors())
return reject(new Error(stats.compilation.errors.join('\n')));
resolve()
});
});
}
const HTMLMap = {
'index.html': 'Index'
};
@ -39,10 +58,13 @@ const JSMap = {
'bundle.js': 'BundleJS'
};
/*
const CSSMap = {
'bundle.css': 'BundleCSS'
};
*/
/*
// There is an issue in the AsyncWebServer where it's apparantly running
// out of memory on simultaneous requests. We'll work around it by
@ -60,11 +82,10 @@ const JSSrc = [
const SCSSSrc = [
'web/site.scss'
]
*/
gulp.task('embedHTML', () =>
function embedHTML()
{
return gulp.src(config.assetsPath + '*.html')
.pipe(print(filepath => { return 'HTML: ' + filepath; }))
@ -81,9 +102,10 @@ gulp.task('embedHTML', () =>
byteArray: true
}))
.pipe(gulp.dest(config.outputPath));
});
}
/*
gulp.task('compileScss', () =>
{
return gulp.src(SCSSSrc)
@ -117,9 +139,10 @@ gulp.task('compileJS', () =>
.pipe(uglify())
.pipe(gulp.dest(config.distPath));
});
*/
gulp.task('embedJS', gulp.series('compileJS', () =>
function embedJS()
{
return gulp.src([config.distPath + 'bundle.js'])
.pipe(gzip({ append: false }))
@ -129,10 +152,11 @@ gulp.task('embedJS', gulp.series('compileJS', () =>
byteArray: true
}))
.pipe(gulp.dest(config.outputPath));
}));
}
gulp.task('embedCSS', gulp.series('compileScss', () =>
/*
function embedCSS()
{
return gulp.src([config.distPath + 'bundle.css'])
.pipe(gzip({ append: false }))
@ -142,18 +166,8 @@ gulp.task('embedCSS', gulp.series('compileScss', () =>
byteArray: true
}))
.pipe(gulp.dest(config.outputPath));
}));
gulp.task('watch', gulp.series(
'compileScss',
'compileJS',
() =>
{
watch(config.assetsPath + '*.scss', () => { gulp.series('compileScss')(); });
watch(config.assetsPath + '*.js', () => { gulp.series('compileJS')(); });
}
));
}
*/
@ -198,7 +212,7 @@ function getVersion(callback)
}
gulp.task('embedVersion', () =>
function embedVersion()
{
return getVersion(version =>
{
@ -229,10 +243,7 @@ gulp.task('embedVersion', () =>
});
})
});
});
gulp.task('embedAssets', gulp.series('embedHTML', 'embedJS', 'embedCSS', 'embedVersion'));
}
@ -309,6 +320,23 @@ function platformio(target)
});
}
gulp.task('upload', gulp.series('embedAssets', () => { return platformio('upload'); }));
gulp.task('build', gulp.series('embedAssets', () => { return platformio(false); }));
gulp.task('default', gulp.series('embedAssets'));
function platformIOUpload()
{
return platformio('upload');
}
function platformIOBuild()
{
return platformio(false);
}
const buildAndEmbedAssets = gulp.series(runWebpack, embedHTML, embedJS, embedVersion);
module.exports = {
upload: gulp.series(buildAndEmbedAssets, platformIOUpload),
build: gulp.series(buildAndEmbedAssets, platformIOBuild),
default: gulp.series(buildAndEmbedAssets)
};

19402
package-lock.json
File diff suppressed because it is too large
View File

37
package.json

@ -5,7 +5,12 @@
"main": "gulpfile.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "node devserver.js"
"upload": "gulp upload",
"build": "gulp build",
"webpack": "gulp",
"dev": "npm-run-all -p start-webpack-devserver start-api-devserver",
"start-webpack-devserver": "webpack-dev-server --config ./webpack.dev.js",
"start-api-devserver": "supervisor -w \"devserver.js\" devserver.js"
},
"repository": {
"type": "git",
@ -14,10 +19,17 @@
"author": "Mark van Renswoude <mark@x2software.net>",
"license": "Unlicense",
"devDependencies": {
"@babel/core": "^7.11.6",
"@babel/preset-env": "^7.11.5",
"axios": "^0.20.0",
"babel-loader": "^8.1.0",
"body-parser": "^1.18.2",
"child_process": "^1.0.2",
"core-js": "^3.6.5",
"css-loader": "^4.3.0",
"express": "^4.16.2",
"file-loader": "^6.1.0",
"gulp": "^4.0.2",
"gulp-clean-css": "^3.9.0",
"gulp-concat": "^2.6.1",
"gulp-debounced-watch": "^1.0.4",
@ -27,16 +39,29 @@
"gulp-print": "^2.0.1",
"gulp-sass": "^3.1.0",
"gulp-uglify": "^3.0.0",
"html-webpack-plugin": "^4.4.1",
"lodash": "^4.17.4",
"node-sass": "^4.14.1",
"npm": "^6.14.8",
"npm-run-all": "^4.1.5",
"path": "^0.12.7",
"sass": "^1.26.5",
"sass-loader": "^8.0.2",
"style-loader": "^1.2.1",
"terser-webpack-plugin": "^4.2.2",
"through2": "^2.0.3",
"vinyl": "^2.1.0",
"vue": "^2.5.13",
"vue": "^2.6.11",
"vue-i18n": "^7.3.3",
"vue-loader": "^15.9.3",
"vue-router": "^3.2.0",
"vue-style-loader": "^4.1.2",
"vue-template-compiler": "^2.6.11",
"vuex": "^3.4.0",
"webpack": "^4.44.2",
"webpack-cli": "^3.3.12",
"webpack-dev-server": "^3.11.0",
"webpack-merge": "^5.1.4",
"yargs": "^16.0.3"
},
"dependencies": {
"gulp": "^4.0.2",
"npm": "^6.14.8"
}
}

108
src/assets/css.h

@ -1,108 +0,0 @@
#ifndef __embed_css
#define __embed_css
#include <pgmspace.h>
const uint8_t EmbeddedBundleCSS[] PROGMEM = {
0x1f,0x8b,0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x0a,0xad,0x59,0xd9,0x8e,0xa3,0x3a,0x1a,0x7e,0x95,0x48,
0xa5,0x96,0xba,0x46,0x80,0x08,0x49,0xa8,0x2a,0xd0,0x19,0xcd,0x68,0xde,0x60,0x2e,0xe6,0xa6,0x55,0x17,
0x06,0x4c,0xb0,0x8a,0x60,0x64,0x9c,0x5a,0x1a,0xf1,0xee,0xf3,0x7b,0x4b,0x6c,0x30,0xe9,0xf4,0x39,0x47,
0xa8,0x16,0x6c,0xff,0xab,0xff,0xe5,0xb3,0x69,0xf8,0xa9,0x1d,0xe9,0x3b,0x66,0x43,0xc9,0x68,0xdb,0x86,
0x05,0x6e,0xd0,0x3b,0xa1,0x2c,0xfc,0xcc,0x4a,0xda,0x71,0x44,0xba,0xbc,0xa0,0x9f,0xe1,0x40,0x7e,0x92,
0xee,0x98,0x15,0x94,0x55,0x98,0x85,0x30,0x92,0xd7,0x30,0x2b,0x86,0x71,0x96,0x26,0xd1,0xe1,0xdb,0xf4,
0x8f,0x20,0x43,0x35,0xc7,0x2c,0xc8,0x0a,0x5c,0x53,0x86,0x47,0x8b,0x8c,0x74,0x0d,0x66,0x84,0x4f,0x05,
0xad,0xbe,0x7e,0x25,0x0c,0x95,0x6f,0x47,0x46,0xcf,0x5d,0x15,0x96,0xb4,0xa5,0x2c,0x7b,0xd8,0xee,0xc5,
0x93,0xeb,0xb7,0xba,0xae,0x95,0xe8,0x1a,0x9d,0x48,0xfb,0x95,0xfd,0x0f,0xb3,0x0a,0x75,0x28,0xf8,0x37,
0x23,0xa8,0x0d,0x06,0xd4,0x0d,0xe1,0x00,0xb2,0x6a,0x4b,0xbf,0x6d,0xb4,0xc3,0x27,0xf5,0xfe,0x81,0xc9,
0xb1,0xe1,0xd9,0x2e,0x8e,0xf3,0x16,0x73,0xd0,0x36,0x1c,0x7a,0x54,0x0a,0x15,0xa3,0x78,0x0b,0x8b,0x5a,
0xd2,0xe1,0xb0,0x51,0x8b,0x80,0x2c,0xef,0x51,0x55,0xc1,0x2c,0x18,0xcc,0x39,0x3d,0x65,0x3b,0x86,0x4f,
0xd3,0xbf,0x4e,0xb8,0x22,0x68,0x03,0x26,0x60,0xdc,0x6d,0x50,0x57,0x6d,0xbe,0x9f,0x48,0x17,0x7e,0x90,
0x8a,0x37,0xd9,0x53,0xfa,0xdc,0x7f,0x3e,0x8e,0xd2,0x50,0x43,0xcc,0x69,0xaf,0x28,0x27,0x34,0x72,0xfc,
0xc9,0xc3,0x0a,0x97,0x94,0x21,0x4e,0x68,0x97,0x75,0xb4,0xc3,0xd3,0x8f,0xf7,0xb0,0x6c,0x29,0x7a,0x7b,
0x1d,0x2b,0x32,0xf4,0x2d,0xfa,0x52,0xc3,0x0f,0xda,0x27,0x98,0x8d,0x57,0xaf,0x64,0x0f,0x49,0x2c,0x9e,
0xfc,0x84,0xd8,0x11,0xc4,0x0a,0xe6,0x09,0x30,0x37,0xaa,0x66,0x5b,0xf1,0x22,0x7d,0xdf,0xa0,0x8a,0x7e,
0x64,0xf1,0x26,0xde,0x1c,0xe2,0xfe,0x73,0xf3,0x50,0x97,0x75,0x5a,0xd6,0xb9,0xda,0xc3,0x6c,0xa0,0x2d,
0xa9,0x36,0x5b,0x31,0x11,0xc7,0xf1,0x5d,0x56,0x59,0x0a,0x59,0xe3,0x46,0x93,0x16,0xd7,0x3c,0x43,0x67,
0x4e,0xcd,0x00,0x93,0x6e,0x14,0x23,0xd3,0x14,0x35,0x18,0x81,0xd4,0xb1,0xa7,0x03,0x91,0x86,0x33,0xdc,
0x82,0x07,0xde,0xb1,0x99,0xd9,0x90,0xd3,0x71,0xac,0xc1,0x0b,0x3c,0x13,0x8c,0x5c,0x1e,0xdb,0x35,0xbf,
0xa3,0xcf,0x8b,0x86,0x4f,0x42,0x43,0xc3,0x2c,0xfa,0x20,0x35,0x19,0x38,0xe2,0xe7,0x61,0x2c,0x5b,0x8c,
0x18,0x44,0x2e,0x6f,0x6c,0x9f,0xa9,0x0d,0xb9,0xc7,0x6a,0x1f,0xcf,0x8b,0x19,0xa8,0x00,0x3f,0x9e,0x39,
0xce,0x95,0xa2,0x71,0x2e,0x78,0xc7,0x17,0x7b,0x6d,0xa2,0x4d,0x44,0xba,0x8a,0x94,0x88,0x53,0x76,0xd9,
0x67,0xd2,0xc9,0x88,0x2b,0x5a,0x5a,0xbe,0xe5,0x4a,0xaa,0xdc,0x3f,0x13,0x82,0x6a,0x2f,0x65,0xce,0x31,
0x54,0x91,0xf3,0x90,0x1d,0xe2,0x6f,0xae,0x6f,0xa2,0x83,0xb0,0xe4,0xb6,0xbc,0x1f,0x15,0xe2,0x28,0x54,
0xc3,0x7f,0xc0,0x26,0x76,0xb8,0xe4,0xb8,0x7a,0x1d,0x97,0xc9,0xb6,0x7b,0x49,0x7f,0x87,0x17,0xd8,0x61,
0xb3,0x9b,0x47,0xd6,0x91,0xe1,0xaf,0x3f,0xa1,0x1a,0x84,0xb1,0x4f,0xb7,0xfa,0x65,0xf7,0x3b,0xcc,0x30,
0x63,0x94,0xf9,0xf8,0x94,0x10,0xee,0x51,0x71,0x86,0x8c,0xee,0x82,0xa8,0x6c,0x70,0xf9,0xb6,0x89,0x44,
0x64,0x43,0x49,0x0a,0xa2,0x8e,0x72,0xe0,0x5c,0xca,0xec,0x0c,0xa2,0x1e,0x75,0xb8,0xdd,0xa8,0x3f,0xa1,
0x48,0xea,0xd9,0x90,0xd2,0x26,0x88,0xc4,0xe6,0x50,0x8b,0xcb,0x07,0x62,0x1d,0x58,0x11,0x68,0x29,0xcd,
0x2e,0x20,0x5d,0x7f,0xe6,0x3f,0xf8,0x57,0x8f,0xff,0x18,0xce,0xc5,0x89,0xf0,0xd7,0x60,0xc0,0x2d,0x18,
0x6b,0xbc,0x26,0xfc,0xa5,0x3c,0xf7,0xb0,0xdd,0x6e,0x67,0xbb,0xbe,0x83,0x34,0xb3,0x72,0x9a,0x74,0x03,
0xe6,0x90,0xd7,0x82,0x86,0x1d,0x0b,0xf4,0x3d,0x39,0x1c,0x02,0xf3,0x13,0x6d,0x1f,0x03,0xb3,0x20,0x14,
0x2b,0x76,0x66,0x55,0x1c,0x88,0x27,0xda,0x5d,0xe7,0xe3,0x55,0x26,0xf1,0xf3,0x63,0xa0,0xe6,0x92,0x19,
0xf9,0xf6,0xf0,0x68,0xdc,0x17,0xa1,0x52,0xe4,0x70,0xa0,0x5f,0x33,0xfd,0xea,0x4e,0xba,0x73,0xd2,0x0f,
0x7a,0xca,0x75,0x87,0xe5,0xa1,0xee,0x7c,0x2a,0x30,0x73,0x86,0x7a,0x34,0x0c,0x1f,0xe0,0x93,0x57,0x8f,
0x27,0x1d,0xde,0x6a,0x46,0xd4,0xd9,0xd7,0x40,0xfc,0x46,0x0c,0xa3,0xdb,0x3e,0xd6,0x61,0x01,0x55,0x50,
0x0e,0x9b,0x39,0xaf,0xb7,0xe7,0xce,0x48,0x0e,0xc6,0x4d,0x5e,0x17,0x4e,0x3a,0x00,0xa4,0x66,0xe3,0x3d,
0x7d,0x6b,0x52,0x4b,0xa1,0x53,0x15,0x6f,0x84,0x87,0xa8,0xef,0xa1,0x7a,0xa1,0xae,0xc4,0xb2,0x27,0xe4,
0xe1,0x89,0xfe,0x5c,0x0c,0xce,0xde,0x2f,0xc1,0x6d,0x0b,0x77,0x1c,0xe6,0xaf,0x3f,0xa6,0x83,0x80,0x3d,
0x60,0xa6,0x69,0xb8,0x55,0x55,0xe5,0x76,0xfb,0xd9,0xc7,0xe2,0xc9,0xcb,0x33,0x1b,0x60,0xba,0xa7,0xa4,
0x83,0x36,0xea,0x34,0x4e,0x59,0x5a,0x4d,0x84,0xd4,0xb4,0x3c,0x0f,0x97,0x00,0x71,0xdf,0x1a,0x01,0x05,
0x02,0x67,0xa1,0xb3,0xce,0x59,0xb6,0xb4,0x42,0x93,0x78,0xe2,0x61,0x75,0x42,0xb2,0x1a,0x57,0x2c,0x3b,
0xc4,0xe2,0xc9,0xe9,0x99,0x0b,0x6b,0xb2,0xf8,0xaf,0x46,0xb9,0xab,0xec,0xea,0x8c,0xa6,0x31,0x5a,0x95,
0x65,0xe9,0x68,0x95,0x3c,0x8b,0xc7,0xe8,0x12,0xf6,0x8c,0x40,0xf9,0xff,0xf2,0x6d,0xaa,0x43,0x95,0xa4,
0x07,0xb4,0x9d,0x53,0xb9,0xbb,0x61,0x46,0x33,0xff,0xe8,0xdf,0xee,0x76,0x47,0xbf,0xf4,0x29,0x29,0xd2,
0x09,0x69,0xa1,0x7e,0x5c,0x14,0x75,0x80,0x0e,0x8f,0x72,0xe0,0x56,0x17,0x8f,0x38,0x74,0xe1,0x7f,0x1a,
0x4e,0x36,0x1a,0x11,0xe5,0x6f,0x56,0x49,0x63,0x77,0x79,0x56,0x13,0x36,0xf0,0xb0,0x6c,0x48,0x5b,0x39,
0xa4,0xf1,0xb2,0x02,0xcb,0x6a,0x09,0x7f,0x67,0x1c,0x5a,0x74,0x61,0x30,0x13,0x25,0x2b,0xaf,0x24,0x9c,
0x0b,0x15,0x8e,0x5a,0x62,0xa1,0xfc,0x67,0x08,0xad,0x0c,0x7f,0x66,0xb0,0x71,0x02,0x24,0x4b,0xbb,0x65,
0x54,0x88,0x46,0x6a,0x21,0xda,0xe7,0x9e,0xe7,0xd2,0x63,0xa8,0x25,0xc7,0x2e,0x2b,0xb1,0xcc,0xc1,0x19,
0x22,0x9c,0x9c,0x5e,0xf6,0x9f,0x0b,0x74,0xbb,0x88,0xad,0xc9,0x27,0xae,0xf2,0x0b,0x80,0x34,0xc2,0xd3,
0x34,0xbd,0x0f,0x17,0xf9,0xd9,0xab,0x35,0x07,0x59,0x42,0xa4,0x27,0x01,0xb1,0x4c,0xae,0x2e,0x6e,0x24,
0xbc,0x3c,0xa1,0xe2,0x79,0x8e,0x58,0xb7,0xb1,0x06,0xa6,0x36,0xee,0x9f,0xd5,0x1c,0x53,0xaf,0xa2,0x03,
0x68,0xaf,0x8d,0xd7,0x40,0x5d,0x21,0xe2,0x05,0xd6,0xfc,0x6d,0xb3,0x46,0x15,0x46,0x4b,0x13,0x36,0xd1,
0x09,0x0f,0x03,0x3a,0xe2,0xf1,0xa3,0x21,0x1c,0xcb,0x63,0x04,0xce,0x7a,0x86,0xdd,0x65,0x91,0x04,0x21,
0x8e,0xbd,0x2f,0x4f,0x3b,0xb4,0x83,0x7c,0x96,0xc0,0x43,0x23,0x07,0x7f,0x3d,0x9e,0x99,0x7b,0x1e,0xc4,
0x71,0x45,0x62,0x06,0x55,0xf6,0x6d,0xc9,0x1d,0xfd,0x60,0xa8,0xb7,0x43,0xc0,0xe3,0x15,0x31,0x34,0x2d,
0x10,0x8f,0x8b,0x5d,0x46,0x4f,0xad,0xbf,0x85,0x56,0x53,0xd8,0x66,0x83,0x56,0xc5,0xff,0x1e,0x80,0xaf,
0x05,0xb6,0xa8,0xc0,0x57,0x71,0xf2,0xcd,0x6f,0xb7,0x9d,0x86,0xd2,0x0a,0xc8,0x05,0x0e,0x0e,0x6d,0x75,
0xb8,0x83,0x75,0x9a,0xa9,0xfa,0x8d,0xab,0xb9,0x35,0x8b,0x71,0xc7,0xaa,0x34,0x16,0x8f,0x61,0x01,0x2a,
0xa0,0xa2,0xc5,0x95,0x21,0x35,0xef,0xa3,0x76,0x3f,0x6c,0x27,0xc8,0x6d,0xe9,0x07,0xae,0xe6,0x24,0xae,
0x4d,0xf3,0x61,0x2b,0x75,0xa7,0x05,0x40,0x74,0xdf,0x05,0x84,0x95,0xc7,0xbc,0x39,0xd4,0x9f,0x13,0x9a,
0x85,0x57,0xc8,0x92,0x2f,0x8f,0x22,0x62,0xf7,0xf7,0x26,0xfb,0xc4,0x3f,0x6a,0xab,0xac,0x9d,0x4a,0x45,
0x11,0xf3,0x7b,0xea,0xa2,0x8a,0xe5,0x30,0xd9,0x92,0xec,0xfc,0xdc,0xce,0x50,0x90,0x40,0x84,0x6e,0x58,
0x19,0x36,0x7e,0xed,0x0e,0x37,0xb5,0x93,0x25,0xd6,0xbf,0xbf,0xae,0xa3,0xf4,0x49,0x63,0x89,0xbc,0x9e,
0x1f,0x4d,0xf5,0x56,0xcc,0x45,0x45,0x49,0x14,0x7a,0xcb,0x39,0x00,0xa5,0xa1,0xa6,0xec,0x94,0x31,0x0a,
0x07,0x05,0xfc,0x3d,0xdc,0x1f,0x2a,0x7c,0x7c,0xb4,0x2d,0x94,0xb0,0x39,0x76,0x81,0x9e,0x83,0xf3,0x2c,
0xbb,0x05,0xab,0xd0,0x84,0x99,0x95,0x7f,0x5b,0xc8,0xb5,0x3f,0x01,0x69,0xe7,0xc0,0xd5,0x83,0xbc,0xae,
0x05,0xd1,0xa9,0x80,0x3a,0x21,0x63,0x88,0x1a,0x73,0xb2,0xb8,0x49,0x7c,0xd7,0x2d,0x8a,0x2d,0xc1,0x36,
0x07,0x9c,0x78,0xc4,0xaf,0x8e,0xbd,0x6c,0x51,0x6f,0xe4,0xb1,0xbd,0xd9,0x8e,0xd7,0xc6,0x95,0x5c,0x17,
0x41,0x27,0x6e,0x12,0x1d,0xc8,0x03,0x69,0x21,0xc7,0x9d,0x2b,0x9b,0xd9,0xca,0x9d,0xdd,0x08,0x97,0xf0,
0x68,0x49,0x7a,0x55,0x5c,0x2a,0xb1,0x1f,0xed,0x15,0x7b,0x76,0xb1,0xc6,0x24,0xed,0xab,0x27,0xdf,0xf3,
0x35,0x99,0xa9,0x78,0x26,0xb7,0x84,0x39,0xb5,0xeb,0x66,0x01,0x96,0x74,0xa1,0x2a,0x79,0xa3,0x73,0x96,
0x4f,0xee,0xbd,0x5f,0x8a,0x1a,0xca,0xc8,0x4f,0xd1,0x73,0x5b,0x0b,0x19,0x4d,0xd6,0xf0,0x66,0xbd,0xc0,
0x3a,0xcb,0x3c,0x21,0xba,0x32,0x7d,0x0d,0xd7,0x95,0x05,0x2a,0x74,0xed,0xc9,0x4b,0x18,0x7b,0xeb,0xbc,
0xba,0xed,0x91,0x96,0xeb,0xe0,0x95,0x15,0xef,0x4a,0xaf,0x2e,0x14,0x6d,0x03,0x61,0x16,0x9a,0xe1,0xcc,
0xe7,0x2e,0x2e,0xb2,0xf6,0x6c,0x16,0x8d,0xfa,0xa6,0x04,0xa4,0x8a,0xc8,0x18,0x67,0x30,0x72,0x09,0xa8,
0xa6,0x08,0xf0,0x2b,0xed,0x45,0xf9,0x1a,0x1c,0x6c,0x78,0xb0,0x0e,0x38,0xce,0xfd,0xd2,0x4d,0x4c,0x26,
0xb3,0x21,0x1a,0xa0,0x62,0x01,0xb4,0x1b,0x17,0x60,0x4d,0x4d,0x5c,0x6f,0xd8,0x56,0x28,0x57,0x4f,0x86,
0xd7,0xf4,0x37,0x75,0x54,0xda,0x3b,0x83,0xb1,0x70,0x60,0x95,0x83,0xcb,0xca,0x60,0x1d,0x7b,0x94,0xa4,
0x2c,0x33,0xa2,0xd4,0x7b,0xc8,0x1b,0x08,0x90,0x55,0xf9,0x7e,0x7d,0x12,0xeb,0x36,0x2b,0xf1,0xdf,0x66,
0xd9,0xba,0xe8,0x2b,0x4a,0x17,0xf9,0x58,0x0a,0x89,0xd3,0xaf,0x2c,0x3d,0x5a,0x99,0xbf,0x5f,0x48,0xc4,
0x70,0xb5,0x62,0xb9,0xd3,0x12,0xf1,0x2e,0xdd,0xa5,0x33,0xa2,0xb9,0x76,0x37,0x08,0x8e,0x22,0xb9,0xef,
0x90,0xb3,0x4b,0x8a,0xa7,0x5d,0xb2,0x20,0xbb,0x25,0x69,0x46,0x52,0xb4,0x67,0x7c,0x87,0xa0,0x7d,0xba,
0x4f,0xcb,0x72,0x4e,0x75,0x4b,0x8e,0xa1,0xd0,0x77,0x5d,0x1e,0x90,0xfb,0x4b,0x84,0xbe,0x88,0xf1,0x8e,
0x8a,0x1b,0x3c,0xbb,0xd6,0x7b,0xb2,0x52,0x26,0x9c,0x53,0xf6,0xe4,0x65,0xdc,0xe8,0x3b,0x02,0x98,0x2b,
0x8d,0xc9,0x77,0x6f,0x37,0x2e,0x8f,0x78,0xfa,0x98,0x97,0x5f,0x3e,0x74,0x08,0x5e,0x06,0x45,0xb8,0xf6,
0xdc,0xec,0xae,0x5e,0x79,0xba,0x26,0x5b,0x9d,0x48,0xd8,0xec,0x5b,0x28,0x6f,0x0b,0x44,0x71,0xb1,0x6a,
0xe4,0xca,0x4a,0x0d,0x41,0x7d,0x73,0xc6,0x91,0x1e,0x85,0xe4,0x07,0x8a,0xf9,0x71,0xf5,0x72,0x60,0x75,
0x6c,0xdb,0xc5,0xe2,0xb9,0xd8,0xae,0xaa,0x95,0xe4,0xa2,0xef,0x34,0xe6,0x4e,0xb5,0x69,0x8b,0x3d,0x3a,
0x3c,0x3b,0x7e,0xd1,0x7d,0xef,0xc6,0x99,0x42,0x7e,0x31,0x88,0x6a,0x60,0x16,0xca,0x1d,0x0f,0xcd,0xfd,
0x8b,0x1c,0x82,0x6d,0x7f,0xc7,0x7a,0x68,0x94,0x60,0x4e,0x01,0x4c,0x2a,0x3e,0xe4,0xf0,0xaf,0x4d,0x74,
0x18,0x6c,0x62,0x87,0x8a,0xd3,0x51,0x2f,0x13,0x11,0x21,0x03,0xdb,0x89,0x23,0x39,0xb2,0x89,0x06,0x68,
0x5a,0xdc,0x73,0x3c,0xbf,0xa1,0xf3,0xfe,0xe5,0x9b,0x4b,0xbe,0x59,0x2b,0xe8,0xaa,0xd3,0xef,0x85,0x13,
0xdd,0xf5,0xef,0x08,0x72,0xee,0x17,0x5f,0x16,0x54,0x0f,0xcb,0xdd,0x93,0x85,0x64,0x82,0xbb,0xea,0x5e,
0x8d,0x57,0xfa,0xee,0x95,0xd1,0xaa,0xee,0x0a,0xb3,0x5b,0xaa,0xcb,0xc5,0x6b,0x8a,0xeb,0x7b,0x94,0x5b,
0x7a,0x2f,0x3b,0x3c,0x94,0xd2,0x01,0xf3,0xff,0x62,0x34,0xcc,0xee,0x72,0x12,0x10,0xfb,0x7f,0x44,0x42,
0x49,0x9a,0xad,0x1c,0x00,0x00};
#endif

5562
src/assets/js.h
File diff suppressed because it is too large
View File

2
src/server/static.cpp

@ -8,7 +8,6 @@
#include "../debug.h"
#include "../assets/html.h"
#include "../assets/js.h"
#include "../assets/css.h"
void handleGzipped(AsyncWebServerRequest *request, const String& contentType, const uint8_t * content, size_t len)
@ -25,5 +24,4 @@ void registerStaticRoutes(AsyncWebServer* server)
server->on("/", HTTP_GET, [](AsyncWebServerRequest *request) { handleGzipped(request, "text/html", EmbeddedIndex, sizeof(EmbeddedIndex)); });
server->on("/bundle.js", HTTP_GET, [](AsyncWebServerRequest *request) { handleGzipped(request, "text/javascript", EmbeddedBundleJS, sizeof(EmbeddedBundleJS)); });
server->on("/bundle.css", HTTP_GET, [](AsyncWebServerRequest *request) { handleGzipped(request, "text/css", EmbeddedBundleCSS, sizeof(EmbeddedBundleCSS)); });
}

615
web/app.js

@ -1,615 +0,0 @@
function startApp()
{
// Source: https://github.com/axios/axios/issues/164
axios.interceptors.response.use(undefined, function axiosRetryInterceptor(err) {
var config = err.config;
// If config does not exist or the retry option is not set, reject
if(!config || !config.retry) return Promise.reject(err);
// Set the variable for keeping track of the retry count
config.__retryCount = config.__retryCount || 0;
// Check if we've maxed out the total number of retries
if(config.__retryCount >= config.retry) {
// Reject with the error
return Promise.reject(err);
}
// Increase the retry count
config.__retryCount += 1;
// Create new promise to handle exponential backoff
var backoff = new Promise(function(resolve) {
setTimeout(function() {
resolve();
}, config.retryDelay || 1);
});
// Return the promise in which recalls axios to retry the request
return backoff.then(function() {
return axios(config);
});
});
Vue.component('check', {
template: '<div class="check" :class="{ checked: value, disabled: disabled }" @keydown="handleKeyDown" @click="handleClick" tabindex="0"><div class="control"><div class="inner"></div></div><div class="label">{{ title }}</div></div>',
props: {
title: String,
value: {
type: Boolean,
default: false
},
disabled: {
type: Boolean,
default: false
}
},
methods: {
handleClick: function()
{
if (this.disabled) return;
this.value = !this.value;
this.$emit('input', this.value);
},
handleKeyDown: function(event)
{
if (event.keyCode == 32)
{
this.handleClick();
event.preventDefault();
}
}
}
});
Vue.component('radio', {
template: '<div class="radio" :class="{ checked: value == id, disabled: disabled }" @keydown="handleKeyDown" @click="handleClick" tabindex="0"><div class="control"><div class="inner"></div></div><div class="label">{{ title }}</div></div>',
props: {
title: String,
value: null,
id: null,
disabled: {
type: Boolean,
default: false
}
},
methods: {
handleClick: function()
{
if (this.disabled) return;
this.value = this.id;
this.$emit('input', this.value);
},
handleKeyDown: function(event)
{
if (event.keyCode == 32)
{
this.handleClick();
event.preventDefault();
}
}
}
});
Vue.component('range', {
template: '<div>' +
'<div class="start">' +
'<span class="value">{{ value.start }}</span>' +
'<div class="slidercontainer">' +
'<input type="range" min="0" max="4094" class="slider" v-model.number="value.start">' +
'</div>' +
'</div>' +
'<div class="end">' +
'<span class="value">{{ value.end }}</span>' +
'<div class="slidercontainer">' +
'<input type="range" min="1" max="4095" class="slider" v-model.number="value.end">' +
'</div>' +
'</div>' +
'</div>',
props: ['value'],
mounted: function()
{
this.oldValue = { start: this.value.start, end: this.value.end };
},
watch: {
value: {
handler: function(newValue)
{
if (newValue.start != this.oldValue.start)
{
if (newValue.start > newValue.end)
{
newValue.end = newValue.start + 1;
this.$emit('input', newValue);
}
}
else if (newValue.end != this.oldValue.end)
{
if (newValue.end < newValue.start)
{
newValue.start = newValue.end - 1;
this.$emit('input', newValue);
}
}
this.oldValue.start = newValue.start;
this.oldValue.end = newValue.end;
},
deep: true
}
}
});
var i18n = new VueI18n({
locale: navigator.language.split('-')[0],
fallbackLocale: 'en',
messages: messages
});
var app = new Vue({
el: '#app',
i18n: i18n,
data: {
notification: null,
loading: true,
saving: false,
settingStatic: false,
loadingIndicator: '|',
uploadProgress: false,
activeTab: 'status',
status: {
systemID: 'loading...',
version: 'loading...',
resetReason: null,
stackTrace: false
},
wifiStatus: {
ap: {
enabled: false,
ip: '0.0.0.0'
},
station: {
enabled: false,
status: 0,
ip: '0.0.0.0'
}
},
connection: {
hostname: null,
accesspoint: true,
station: false,
ssid: null,
password: null,
dhcp: true,
ip: null,
subnetmask: null,
gateway: null
},
system: {
pins: {
ledAP: null,
ledSTA: null,
apButton: null,
},
ledCount: null
},
static: {
r: 0,
g: 0,
b: 0,
w: 0
}
},
created: function()
{
var self = this;
self.notificationTimer = null;
self.disableSetStatic = false;
self.setStaticTimer = false;
document.title = i18n.t('title');
var hash = window.location.hash.substr(1);
if (hash)
self.activeTab = hash;
self.startLoadingIndicator();
self.updateWiFiStatus();
// Sequential loading of all the settings makes sure
// we don't overload the ESP8266 with requests, as that
// can cause it to run out of memory easily.
// This is a horrible way to implement it, but I don't feel like
// including a big library or working out a clean short solution
// at the moment, and it works :)
self.loadStatus().then(function()
{
self.loadConnection().then(function()
{
self.loadSystem().then(function()
{
self.stopLoadingIndicator();
self.loading = false;
});
});
});
},
methods: {
showNotification: function(message, error)
{
var self = this;
self.notification = {
message: message,
error: error
};
if (self.notificationTimer != null)
clearTimeout(self.notificationTimer);
self.notificationTimer = setTimeout(function()
{
self.notification = null;
self.notificationTimer = null;
}, 5000);
},
hideNotification: function()
{
var self = this;
self.notification = null;
if (self.notificationTimer != null)
{
clearTimeout(self.notificationTimer);
self.notificationTimer = null;
}
},
handleAPIError: function(messageId, error)
{
var self = this;
console.log(error);
var errorMessage = '';
if (error.response)
{
errorMessage = 'HTTP response code ' + error.response.status;
}
else if (error.request)
{
errorMessage = 'No response';
}
else
{
errorMessage = error.message;
}
self.showNotification(i18n.t(messageId) + '\n\n' + errorMessage, true);
},
loadStatus: function()
{
var self = this;
return axios.get('/api/status', { retry: 10, retryDelay: 1000 })
.then(function(response)
{
if (typeof response.data == 'object')
self.status = response.data;
})
.catch(self.handleAPIError.bind(self, 'error.loadStatus'));
},
loadConnection: function()
{
var self = this;
return axios.get('/api/connection', { retry: 10, retryDelay: 1000 })
.then(function(response)
{
if (typeof response.data == 'object')
self.connection = response.data;
})
.catch(self.handleAPIError.bind(self, 'error.loadConnection'));
},
loadSystem: function()
{
var self = this;
return axios.get('/api/system', { retry: 10, retryDelay: 1000 })
.then(function(response)
{
if (typeof response.data == 'object')
self.system = response.data;
})
.catch(self.handleAPIError.bind(self, 'error.loadSystem'));
},
applyConnection: function()
{
var self = this;
if (self.saving) return;
self.saving = true;
axios.post('/api/connection', {
hostname: self.connection.hostname,
accesspoint: self.connection.accesspoint,
station: self.connection.station,
ssid: self.connection.ssid,
password: self.connection.password,
dhcp: self.connection.dhcp,
ip: self.connection.ip,
subnetmask: self.connection.subnetmask,
gateway: self.connection.gateway,
}, { retry: 10, retryDelay: 1000, headers: { 'Content-Type': 'application/json' } })
.then(function(response)
{
})
.catch(self.handleAPIError.bind(self, 'error.applyConnection'))
.then(function()
{
self.saving = false;
});
},
applySystem: function()
{
var self = this;
if (self.saving) return;
self.saving = true;
axios.post('/api/system', self.system, { retry: 10, retryDelay: 1000, headers: { 'Content-Type': 'application/json' } })
.then(function(response)
{
self.showNotification(i18n.t('rebootPending'));
})
.catch(self.handleAPIError.bind(self, 'error.applySystem'))
.then(function()
{
self.saving = false;
});
},
startLoadingIndicator: function()
{
var self = this;
self.loadingStage = 0;
self.loadingTimer = setInterval(function()
{
self.loadingStage++;
switch (self.loadingStage)
{
case 1: self.loadingIndicator = '/'; break;
case 2: self.loadingIndicator = '-'; break;
case 3: self.loadingIndicator = '\\'; break;
case 4: self.loadingIndicator = '|'; self.loadingStage = 0; break;
}
}, 250);
},
stopLoadingIndicator: function()
{
clearInterval(this.loadingTimer);
},
getWiFiStationStatus: function()
{
if (!this.wifiStatus.station.enabled)
return 'disconnected';
switch (this.wifiStatus.station.status)
{
case 0: // WL_IDLE_STATUS
case 2: // WL_SCAN_COMPLETED
return 'connecting';
case 1: // WL_NO_SSID_AVAIL
case 4: // WL_CONNECT_FAILED
case 5: // WL_CONNECTION_LOST
return 'error';
case 3: // WL_CONNECTED
return 'connected';
case 6: // WL_DISCONNECTED
default:
return 'disconnected';
}
},
getWiFiStationStatusText: function()
{
if (!this.wifiStatus.station.enabled)
return i18n.t('wifiStatus.stationmode.disabled');
switch (this.wifiStatus.station.status)
{
case 0: // WL_IDLE_STATUS
return i18n.t('wifiStatus.stationmode.idle');
case 1: // WL_NO_SSID_AVAIL
return i18n.t('wifiStatus.stationmode.noSSID');
case 2: // WL_SCAN_COMPLETED
return i18n.t('wifiStatus.stationmode.scanCompleted');
case 3: // WL_CONNECTED
return this.wifiStatus.station.ip;
case 4: // WL_CONNECT_FAILED
return i18n.t('wifiStatus.stationmode.connectFailed');
case 5: // WL_CONNECTION_LOST
return i18n.t('wifiStatus.stationmode.connectionLost');
case 6: // WL_DISCONNECTED
default:
return i18n.t('wifiStatus.stationmode.disconnected');
}
},
updateWiFiStatus: function()
{
var self = this;
if (!self.saving)
{
axios.get('/api/connection/status', { retry: 10, retryDelay: 1000 })
.then(function(response)
{
if (typeof response.data == 'object')
self.wifiStatus = response.data;
})
.catch(self.handleAPIError.bind(self, 'error.updateWiFiStatus'))
.then(function()
{
setTimeout(self.updateWiFiStatus, 5000);
});
}
else
setTimeout(self.updateWiFiStatus, 5000);
},
uploadFirmware: function()
{
var self = this;
if (self.saving) return;
self.saving = true;
self.uploadProgress = 0;
var data = new FormData();
data.append('file', document.getElementById('firmwareFile').files[0]);
var config = {
timeout: 360000,
onUploadProgress: function(progressEvent)
{
self.uploadProgress = Math.round((progressEvent.loaded * 100) / progressEvent.total);
}
};
axios.post('/api/firmware', data, config)
.then(function(response)
{
self.showNotification(i18n.t('rebootPending'));
})
.catch(self.handleAPIError.bind(self, 'error.uploadFirmware'))
.then(function()
{
self.uploadProgress = false;
self.saving = false;
document.getElementById('firmware').reset();
});
},
deleteStackTrace: function()
{
var self = this;
return axios.get('/api/stacktrace/delete', { retry: 10, retryDelay: 1000 })
.then(function(response)
{
self.status.resetReason = 0;
self.status.stackTrace = false;
})
.catch(self.handleAPIError.bind(self, 'error.stackTraceDeleteError'));
},
staticOff: function()
{
this.static = {
r: 0,
g: 0,
b: 0,
w: 0
};
},
staticChanged: function()
{
var self = this;
if (self.loading || self.disableStaticChanged) return;
if (self.setStaticTimer === false)
self.setStaticTimer = setTimeout(function() { self.setStatic(); }, 200);
},
setStatic: function()
{
var self = this;
if (self.settingStatic)
self.setStaticTimer = setTimeout(function() { self.setStatic(); }, 200);
self.settingStatic = true;
self.setStaticTimer = false;
axios.get('/api/set/static', { params: this.static })
.then(function(response)
{
})
.catch(self.handleAPIError.bind(self, 'error.setColor'))
.then(function()
{
self.settingStatic = false;
});
},
},
computed: {
hasResetError: function()
{
var self = this;
/*
REASON_DEFAULT_RST = 0 normal startup by power on
REASON_WDT_RST = 1 hardware watch dog reset
REASON_EXCEPTION_RST = 2 exception reset, GPIO status wont change
REASON_SOFT_WDT_RST = 3 software watch dog reset, GPIO status wont change
REASON_SOFT_RESTART = 4 software restart ,system_restart , GPIO status wont change
REASON_DEEP_SLEEP_AWAKE = 5 wake up from deep-sleep
REASON_EXT_SYS_RST = 6 system reset
*/
return (self.status.resetReason === 1 ||
self.status.resetReason === 2 ||
self.status.resetReason === 3 ||
self.status.stackTrace);
}
},
watch: {
static: {
handler: function(newValue) { this.staticChanged(); },
deep: true
}
}
});
}

1
web/dist/bundle.css
File diff suppressed because it is too large
View File

1
web/dist/bundle.js
File diff suppressed because it is too large
View File

201
web/index.html

@ -1,201 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>RGBWifi</title>
<meta name="theme-color" content="#000000">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="bundle.css">
<script src="bundle.js"></script>
</head>
<body>
<div id="app">
<div v-cloak>
<div class="notificationContainer">
<div class="notification" :class="{ error: notification != null && notification.error }" v-if="notification != null" @click.prevent="hideNotification">
<span class="message">{{ notification.message }}</span>
</div>
</div>
<div id="container">
<div class="header">
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6N0E2MTY4NzJGQjFBMTFFQThERTJFM0JGMzk3MjRDRDUiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6N0E2MTY4NzNGQjFBMTFFQThERTJFM0JGMzk3MjRDRDUiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo3QTYxNjg3MEZCMUExMUVBOERFMkUzQkYzOTcyNENENSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo3QTYxNjg3MUZCMUExMUVBOERFMkUzQkYzOTcyNENENSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PgI/6YAAAAfkSURBVHjazFpbbBRV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" />
<h1>{{ $t('title') }}</h1>
<h2>{{ status.systemID !== null ? $t('systemID') + ': ' + status.systemID : '' }}</h2>
<div class="wifistatus">
<div class="connection">
<div class="indicator" :data-status="wifiStatus.ap.enabled ? 'connected' : 'disconnected'"></div> {{ $t('wifiStatus.accesspoint.title') }} {{ wifiStatus.ap.enabled ? wifiStatus.ap.ip : $t('wifiStatus.accesspoint.disabled') }}
</div>
<div class="connection">
<div class="indicator" :data-status="getWiFiStationStatus()"></div> {{ $t('wifiStatus.stationmode.title') }} {{ getWiFiStationStatusText() }}
</div>
</div>
</div>
<div v-if="loading" class="loading">
{{ $t('loading') }} {{ loadingIndicator }}
</div>
<div v-if="!loading">
<div class="warning" v-if="hasResetError">
<p>
{{ $t('error.resetError') }}
</p>
<p class="resetReason">
{{ $t('error.resetReason.' + status.resetReason) }}
</p>
<p v-if="status.stackTrace">
{{ $t('error.stackTrace') }}
</p>
<a class="button button-primary" href="/api/stacktrace/get" v-if="status.stackTrace">{{ $t('error.stackTraceDownload') }}</a>
<a class="button" @click="deleteStackTrace">{{ $t('error.stackTraceDelete') }}</a>
</div>
<div class="navigation tabs">
<a class="button" :class="{ 'active': activeTab == 'status' }" @click="activeTab = 'status'">{{ $t('status.tabTitle') }}</a><a class="button" :class="{ 'active': activeTab == 'connection' }" @click="activeTab = 'connection'">{{ $t('connection.tabTitle') }}</a><a class="button" :class="{ 'active': activeTab == 'system' }" @click="activeTab = 'system'">{{ $t('system.tabTitle') }}</a>
</div>
<div v-if="activeTab == 'status'">
<!--
Status tab
-->
<h3>{{ $t('status.title') }}</h3>
<div class="slidercontainer">
<input type="range" min="0" max="255" class="slider red" v-model.number="static.r">
</div>
<div class="slidercontainer">
<input type="range" min="0" max="255" class="slider green" v-model.number="static.g">
</div>
<div class="slidercontainer">
<input type="range" min="0" max="255" class="slider blue" v-model.number="static.b">
</div>
<div class="slidercontainer">
<input type="range" min="0" max="255" class="slider white" v-model.number="static.w">
</div>
<div class="buttons">
<a class="button button-secondary" @click.prevent="staticOff">{{ $t('status.staticOff') }}</a>
</div>
</div>
<div v-if="activeTab == 'connection'">
<!--
Connection tab
-->
<form @submit.prevent="applyConnection">
<h3>{{ $t('connection.title') }}</h3>
<check v-model.boolean="connection.accesspoint" :title="$t('connection.accesspoint')"></check>
<span class="hint">{{ $t('connection.accesspointHint') }}</span>
<check v-model.boolean="connection.station" :title="$t('connection.stationmode')"></check>
<span class="hint">{{ $t('connection.stationmodeHint') }}</span>
<label for="ssid">{{ $t('connection.ssid') }}</label>
<input type="text" id="ssid" v-model="connection.ssid" :disabled="!connection.station">
<label for="password">{{ $t('connection.password') }}</label>
<input type="password" id="password" v-model="connection.password" :disabled="!connection.station">
<check v-model.boolean="connection.dhcp" :disabled="!connection.station" :title="$t('connection.dhcp')" class="form-control"></check>
<span class="hint">{{ $t('connection.dhcpHint') }}</span>
<div class="suboptions">
<label for="ip">{{ $t('connection.ipaddress') }}</label>
<input type="text" id="ip" v-model="connection.ip" :disabled="!connection.station || connection.dhcp">
<label for="subnetmask">{{ $t('connection.subnetmask') }}</label>
<input type="text" id="subnetmask" v-model="connection.subnetmask" :disabled="!connection.station || connection.dhcp">
<label for="gateway">{{ $t('connection.gateway') }}</label>
<input type="text" id="gateway" v-model="connection.gateway" :disabled="!connection.station || connection.dhcp">
</div>
<label for="hostname">{{ $t('connection.hostname') }}</label>
<input type="text" :placeholder="$t('connection.hostnamePlaceholder')" id="hostname" v-model="connection.hostname" :disabled="!connection.station">
<span class="hint">{{ $t('connection.hostnameHint') }}</span>
<div class="buttons">
<input type="submit" :disabled="saving" :value="saving ? $t('applyButtonSaving') : $t('applyButton')">
</div>
</form>
</div>
<div v-if="activeTab == 'system'">
<!--
System tab
-->
<form @submit.prevent="uploadFirmware">
<h3>{{ $t('system.firmwareTitle') }}</h3>
<input type="file" id="firmwareFile">
<div class="buttons">
<input type="submit" :disabled="saving" :value="saving ? $t('applyButtonSaving') : $t('applyButton')">
</div>
<div v-if="uploadProgress !== false">
{{ uploadProgress }}%
</div>
</form>
<form @submit.prevent="applySystem">
<h3>{{ $t('system.pinsTitle') }}</h3>
<div class="horizontal">
<label for="pinLEDAP">{{ $t('system.pinLEDAP') }}</label>
<input type="number" id="pinLEDAP" v-model.number="system.pins.ledAP">
</div>
<div class="horizontal">
<label for="pinLEDSTA">{{ $t('system.pinLEDSTA') }}</label>
<input type="number" id="pinLEDSTA" v-model.number="system.pins.ledSTA">
</div>
<div class="horizontal">
<label for="pinAPButton">{{ $t('system.pinAPButton') }}</label>
<input type="number" id="pinAPButton" v-model.number="system.pins.apButton">
</div>
<h3>{{ $t('system.ledStripTitle') }}</h3>
<div class="horizontal">
<label for="ledCount">{{ $t('system.ledCount') }}</label>
<input type="number" id="ledCount" v-model.number="system.ledCount">
</div>
<div class="buttons">
<input type="submit" :disabled="saving" :value="saving ? $t('applyButtonSaving') : $t('applyButton')">
</div>
</form>
</div>
</div>
<div class="clearfix"></div>
</div>
<div class="version">
{{ $t('copyright') }}<br>
{{ status.version !== null ? $t('firmwareVersion') + status.version : '' }}
</div>
</div>
</div>
<script language="javascript">
startApp();
</script>
</body>
</html>

207
web/lang.js

@ -1,207 +0,0 @@
var messages = {
en: {
title: 'RGBWifi',
systemID: 'System ID',
firmwareVersion: 'Firmware version: ',
copyright: 'Copyright © 2020 Mark van Renswoude',
loading: 'Please wait, loading configuration...',
rebootPending: 'The system will be rebooted, please refresh this page afterwards',
applyButton: 'Apply',
applyButtonSaving: 'Saving...',
deviceTime: 'Time: ',
wifiStatus: {
accesspoint: {
title: 'AP: ',
disabled: 'Disabled'
},
stationmode: {
title: 'WiFi: ',
disabled: 'Disabled',
idle: 'Idle',
noSSID: 'SSID not found',
scanCompleted: 'Scan completed',
connectFailed: 'Failed to connect',
connectionLost: 'Connection lost',
disconnected: 'Disconnected'
}
},
status: {
tabTitle: 'Status',
title: 'Current status',
staticOff: 'Off'
},
connection: {
tabTitle: 'Connection',
title: 'Connection parameters',
accesspoint: 'Enable access point',
accesspointHint: 'Allows for a direct connection from your device to this RGBWifi module for configuration purposes. The RGBWifi configuration is available on http://192.168.1.4/ when you are connected to it. Turn it off as soon as station mode is configured, as it is not secured in any way. You can always turn this option back on by pushing the access point button until the LED lights up.',
stationmode: 'Enable station mode',
stationmodeHint: 'Connect this RGBWifi module to your own WiFi router. Please enter the SSID, password and further configuration below.',
ssid: 'SSID',
password: 'Password',
dhcp: 'Use DHCP',
dhcpHint: 'Automatically assigns an IP address to this RGBWifi module. You probably want to keep this on unless you know what you\'re doing.',
ipaddress: 'IP address',
subnetmask: 'Subnet mask',
gateway: 'Gateway',
hostname: 'Hostname',
hostnameHint: 'If specified, this module is available at <hostname>.local if your device supports mDNS (at the time of writing, Android does not).',
hostnamePlaceholder: 'Default: mac address'
},
system: {
tabTitle: 'System',
pinsTitle: 'Hardware pinout',
ledStripTitle: 'LED strip',
firmwareTitle: 'Firmware update',
pinLEDAP: 'Access Point status LED pin (+3.3v)',
pinLEDSTA: 'Station Mode status LED pin (+3.3v)',
pinAPButton: 'Enable Access Point button pin (active low)',
ledCount: 'Number of LEDs on strip'
},
error: {
loadStatus: 'Could not load system status',
loadConnection: 'Could not load connection settings',
loadSystem: 'Could not load system settings',
applyConnection: 'Could not save connection settings',
applySystem: 'Could not save system settings',
updateWiFiStatus: 'Could not retrieve WiFi status',
uploadFirmware: 'Error while uploading firmware',
setColor: 'Could not set color',
resetError: 'The system reports that it has been reset unexpectedly. The last power up status is:',
resetReason: {
0: 'Normal startup',
1: 'Unresponsive, reset by hardware watchdog',
2: 'Unhandled exception',
3: 'Unresponsive, reset by software watchdog',
4: 'System restart requested',
5: 'Wake up from deep sleep',
6: 'System reset'
},
stackTrace: 'A stack trace is available. Please send it to your nearest developer and/or delete it from this RGBWifi module to remove this message.',
stackTraceDownload: 'Download',
stackTraceDelete: 'Hide',
stackTraceDeleteError: 'Could not remove stack trace'
}
},
nl: {
title: 'RGBWifi',
systemID: 'Systeem ID',
firmwareVersion: 'Firmware versie: ',
copyright: 'Copyright © 2020 Mark van Renswoude',