Browse Source

Vue single file component implementation

- Using webpack for bundling
develop
Mark van Renswoude 2 years 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 @@ @@ -0,0 +1,3 @@
> 1%
last 2 versions
not dead

1
.gitignore vendored

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

5
babel.config.js

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

24
devserver.js

@ -13,7 +13,7 @@ app.use(bodyParser.json()); @@ -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) @@ -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) @@ -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) @@ -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 = { @@ -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) @@ -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'); @@ -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 = { @@ -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 = { @@ -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 = [ @@ -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', () => @@ -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', () => @@ -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', () => @@ -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', () => @@ -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) @@ -198,7 +212,7 @@ function getVersion(callback)
}
gulp.task('embedVersion', () =>
function embedVersion()
{
return getVersion(version =>
{
@ -229,10 +243,7 @@ gulp.task('embedVersion', () => @@ -229,10 +243,7 @@ gulp.task('embedVersion', () =>
});
})
});
});
gulp.task('embedAssets', gulp.series('embedHTML', 'embedJS', 'embedCSS', 'embedVersion'));
}
@ -309,6 +320,23 @@ function platformio(target) @@ -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 generated

File diff suppressed because it is too large Load Diff

37
package.json

@ -5,7 +5,12 @@ @@ -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 @@ @@ -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 @@ @@ -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 @@ @@ -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 Load Diff

2
src/server/static.cpp

@ -8,7 +8,6 @@ @@ -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) @@ -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 @@ @@ -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 vendored

File diff suppressed because one or more lines are too long

1
web/dist/bundle.js vendored

File diff suppressed because one or more lines are too long

201
web/index.html

@ -1,201 +0,0 @@ @@ -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 @@ @@ -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'
},