Merge branch 'feature/VueSFC' into develop
This commit is contained in:
commit
5d7db8bb25
|
@ -0,0 +1,3 @@
|
|||
> 1%
|
||||
last 2 versions
|
||||
not dead
|
|
@ -1,3 +1,4 @@
|
|||
node_modules
|
||||
bin
|
||||
web/dist
|
||||
.pio
|
|
@ -0,0 +1,5 @@
|
|||
module.exports = {
|
||||
presets: [
|
||||
'@babel/preset-env'
|
||||
]
|
||||
}
|
24
devserver.js
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
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)
|
||||
};
|
||||
|
|
File diff suppressed because it is too large
Load Diff
37
package.json
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
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
5562
src/assets/js.h
File diff suppressed because it is too large
Load Diff
|
@ -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
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 won’t change
|
||||
REASON_SOFT_WDT_RST = 3 software watch dog reset, GPIO status won’t change
|
||||
REASON_SOFT_RESTART = 4 software restart ,system_restart , GPIO status won’t 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
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
201
web/index.html
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
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',
|
||||
loading: 'Een ogenblik geduld, bezig met laden van configuratie...',
|
||||
rebootPending: 'Het systeem wordt opnieuw opgestart, ververse deze pagina nadien',
|
||||
|
||||
applyButton: 'Opslaan',
|
||||
applyButtonSaving: 'Bezig met opslaan...',
|
||||
deviceTime: 'Tijd: ',
|
||||
|
||||
wifiStatus: {
|
||||
accesspoint: {
|
||||
title: 'AP: ',
|
||||
disabled: 'Uitgeschakeld'
|
||||
},
|
||||
|
||||
stationmode: {
|
||||
title: 'WiFi: ',
|
||||
disabled: 'Uitgeschakeld',
|
||||
idle: 'Slaapstand',
|
||||
noSSID: 'SSID niet gevonden',
|
||||
scanCompleted: 'Scan afgerond',
|
||||
connectFailed: 'Kan geen verbinding maken',
|
||||
connectionLost: 'Verbinding verloren',
|
||||
disconnected: 'Niet verbonden'
|
||||
}
|
||||
},
|
||||
|
||||
status: {
|
||||
tabTitle: 'Status',
|
||||
title: 'Huidige status',
|
||||
|
||||
staticOff: 'Uit'
|
||||
},
|
||||
|
||||
connection: {
|
||||
tabTitle: 'Verbinding',
|
||||
title: 'Verbinding configuratie',
|
||||
|
||||
accesspoint: 'Access point inschakelen',
|
||||
accesspointHint: 'Maakt het mogelijk om een directe connectie vanaf een apparaat naar deze RGBWifi module te maken om de module te configureren. De RGBWifi module is te benaderen via http://192.168.1.4/ nadat je connectie hebt gemaakt. Schakel deze optie uit na het configureren, aangezien deze niet beveiligd is. Je kunt deze optie ook inschakelen door op de Access point knop te drukken totdat de LED aan gaat.',
|
||||
|
||||
stationmode: 'Verbinding met WiFi maken',
|
||||
stationmodeHint: 'Verbind deze RGBWifi module aan je eigen WiFi router. Vul hieronder het SSID en wachtwoord in, en configureer eventuel de overige opties.',
|
||||
|
||||
ssid: 'SSID',
|
||||
password: 'Wachtwoord',
|
||||
|
||||
dhcp: 'Gebruik DHCP',
|
||||
dhcpHint: 'Automatisch een IP adres toewijzen aan deze RGBWifi module. Waarschijnlijk wil je deze optie aan laten, tenzij je weet waar je mee bezig bent.',
|
||||
|
||||
ipaddress: 'IP adres',
|
||||
subnetmask: 'Subnet masker',
|
||||
gateway: 'Gateway',
|
||||
hostname: 'Hostnaam',
|
||||
hostnameHint: 'Indien ingevuld is deze module te bereiken op <hostnaam>.local als je apparaat mDNS ondersteund mDNS (op het moment van schrijven ondersteund Android dit niet).',
|
||||
hostnamePlaceholder: 'Standaard: mac adres'
|
||||
},
|
||||
|
||||
system: {
|
||||
tabTitle: 'Systeem',
|
||||
pinsTitle: 'Hardware aansluitingen',
|
||||
ledStripTitle: 'LED strip',
|
||||
firmwareTitle: 'Firmware bijwerken',
|
||||
|
||||
pinLEDAP: 'Access Point status LED pin (+3.3v)',
|
||||
pinLEDSTA: 'WiFi status LED pin (+3.3v)',
|
||||
pinAPButton: 'Access Point inschakelen knop pin (actief laag)',
|
||||
|
||||
ledCount: 'Aantal LEDs op strip'
|
||||
},
|
||||
|
||||
error: {
|
||||
loadStatus: 'Kan systeemstatus niet ophalen',
|
||||
loadConnection: 'Kan verbinding instellingen niet ophalen',
|
||||
loadSystem: 'Kan systeem instellingen niet ophalen',
|
||||
applyConnection: 'Kan verbinding instellingen niet opslaan',
|
||||
applySystem: 'Kan systeem instellingen niet opslaan',
|
||||
updateWiFiStatus: 'Kan WiFi status niet ophalen',
|
||||
uploadFirmware: 'Fout tijdens bijwerken van firmware',
|
||||
|
||||
setColor: 'Kan kleur niet zetten',
|
||||
|
||||
resetError: 'Het systeem is onverwachts herstart. De laatste status is:',
|
||||
resetReason: {
|
||||
0: 'Normaal opgestart',
|
||||
1: 'Reageert niet, herstart door hardware watchdog',
|
||||
2: 'Onafgehandelde fout',
|
||||
3: 'Reageert niet, herstart door software watchdog',
|
||||
4: 'Herstart verzoek door systeem',
|
||||
5: 'Wakker geworden uit diepe slaap',
|
||||
6: 'Systeem gereset'
|
||||
},
|
||||
stackTrace: 'Een stack trace is beschikbaar. Stuur het naar de dichtsbijzijnde ontwikkelaar en/of verwijder het van deze RGBWifi module om dit bericht te verbergen.',
|
||||
stackTraceDownload: 'Downloaden',
|
||||
stackTraceDelete: 'Verbergen',
|
||||
|
||||
stackTraceDeleteError: 'Kan stack trace niet verwijderen'
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="theme-color" content="#000000">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
</html>
|
723
web/site.scss
723
web/site.scss
|
@ -1,723 +0,0 @@
|
|||
@import "variables.scss";
|
||||
|
||||
|
||||
html
|
||||
{
|
||||
overscroll-behavior-x: contain;
|
||||
box-sizing: border-box;
|
||||
font-size: 62.5%;
|
||||
}
|
||||
|
||||
*, *:before, *:after
|
||||
{
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
body
|
||||
{
|
||||
overscroll-behavior-x: contain;
|
||||
background-color: rgb(20, 20, 20);
|
||||
color: white;
|
||||
font-family: 'Verdana', 'Arial', sans-serif;
|
||||
font-size: 1.3em;
|
||||
font-weight: 300;
|
||||
letter-spacing: .01em;
|
||||
line-height: 1.3;
|
||||
|
||||
padding-bottom: 3rem;
|
||||
|
||||
@media #{$mediumScreen}
|
||||
{
|
||||
padding-top: 3rem;
|
||||
}
|
||||
}
|
||||
|
||||
a
|
||||
{
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Hide VueJS container until the template has been processed
|
||||
*/
|
||||
[v-cloak]
|
||||
{
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#container
|
||||
{
|
||||
background: $containerBackground;
|
||||
margin-top: 2rem;
|
||||
padding: 1rem;
|
||||
|
||||
box-shadow: 0 0 50px $containerShadowColor;
|
||||
border: solid 1px black;
|
||||
|
||||
@media #{$mediumScreen}
|
||||
{
|
||||
width: 768px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.header
|
||||
{
|
||||
position: relative;
|
||||
|
||||
img
|
||||
{
|
||||
float: left;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.wifistatus
|
||||
{
|
||||
@media #{$smallScreen}
|
||||
{
|
||||
clear: both;
|
||||
margin-top: 3rem;
|
||||
}
|
||||
|
||||
@media #{$mediumScreen}
|
||||
{
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.indicator
|
||||
{
|
||||
display: inline-block;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
border-radius: 50%;
|
||||
margin-right: 0.5rem;
|
||||
|
||||
&[data-status=connected] { background-color: #339966; }
|
||||
&[data-status=disconnected] { border: solid 1px #808080; }
|
||||
&[data-status=connecting] { background-color: #ff9933; }
|
||||
&[data-status=error] { background-color: #cc0000; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
%outset
|
||||
{
|
||||
border: 1px solid #111111;
|
||||
border-radius: 3px;
|
||||
box-shadow: inset 0 1px rgba(255,255,255,0.1), inset 0 -1px 3px rgba(0,0,0,0.3), inset 0 0 0 1px rgba(255,255,255,0.08), 0 1px 2px rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
|
||||
%inset
|
||||
{
|
||||
border: 1px solid #111111;
|
||||
border-color: black #111111 #111111;
|
||||
box-shadow: inset 0 1px 2px rgba(0,0,0,0.25),0 1px rgba(255,255,255,0.08);
|
||||
}
|
||||
|
||||
|
||||
button, input
|
||||
{
|
||||
font-family: 'Verdana', 'Arial', sans-serif;
|
||||
}
|
||||
|
||||
|
||||
@mixin removeSafariStyling
|
||||
{
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
}
|
||||
|
||||
input
|
||||
{
|
||||
@include removeSafariStyling;
|
||||
}
|
||||
|
||||
button, .button, input[type=submit]
|
||||
{
|
||||
@extend %outset;
|
||||
|
||||
display: inline-block;
|
||||
padding: 0 12px;
|
||||
color: $buttonTextColor;
|
||||
background: $buttonBackground;
|
||||
cursor: pointer;
|
||||
line-height: 3rem;
|
||||
|
||||
&:hover, &:focus, &.focus
|
||||
{
|
||||
color: $buttonHoverTextColor;
|
||||
background: $buttonHoverBackground;
|
||||
outline: none
|
||||
}
|
||||
|
||||
&:active, &.active
|
||||
{
|
||||
@extend %inset;
|
||||
|
||||
color: $buttonActiveTextColor;
|
||||
background: $buttonActiveBackground;
|
||||
}
|
||||
}
|
||||
|
||||
input[type=submit], .button-primary
|
||||
{
|
||||
background: $buttonPrimaryBackground;
|
||||
|
||||
&:hover, &:focus, &.focus
|
||||
{
|
||||
background: $buttonPrimaryHoverBackground;
|
||||
}
|
||||
}
|
||||
|
||||
a.button
|
||||
{
|
||||
text-decoration: none
|
||||
}
|
||||
|
||||
|
||||
.navigation
|
||||
{
|
||||
clear: both;
|
||||
margin-top: 3rem;
|
||||
}
|
||||
|
||||
.tabs
|
||||
{
|
||||
&>.button
|
||||
{
|
||||
margin-left: -1px;
|
||||
border-radius: 0;
|
||||
|
||||
&:first-child
|
||||
{
|
||||
margin-left: 0;
|
||||
border-radius: 3px 0 0 3px
|
||||
}
|
||||
|
||||
&:last-child
|
||||
{
|
||||
border-radius: 0 3px 3px 0
|
||||
}
|
||||
|
||||
&:focus
|
||||
{
|
||||
position: relative;
|
||||
z-index: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.version
|
||||
{
|
||||
color: $versionTextColor;
|
||||
font-size: 8pt;
|
||||
text-align: center;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
|
||||
.notificationContainer
|
||||
{
|
||||
position: fixed;
|
||||
top: 2rem;
|
||||
z-index: 666;
|
||||
|
||||
@media #{$mediumScreen}
|
||||
{
|
||||
width: 512px;
|
||||
left: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.notification
|
||||
{
|
||||
@extend %outset;
|
||||
|
||||
background: $notificationBackground;
|
||||
/* border: solid 1px $notificationBorderColor;*/
|
||||
box-shadow: 0 0 10px black;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
padding: .5em;
|
||||
margin-bottom: 2rem;
|
||||
|
||||
position: relative;
|
||||
|
||||
@media #{$mediumScreen}
|
||||
{
|
||||
left: -50%;
|
||||
}
|
||||
|
||||
.message
|
||||
{
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
|
||||
&.error
|
||||
{
|
||||
background: $notificationErrorBackground;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.check, .radio
|
||||
{
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
white-space: nowrap;
|
||||
margin-top: .5em;
|
||||
margin-bottom: .5em;
|
||||
|
||||
.control
|
||||
{
|
||||
@extend %outset;
|
||||
background: $checkRadioBackground;
|
||||
display: inline-block;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.label
|
||||
{
|
||||
display: inline-block;
|
||||
margin-left: .5em;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
&.checked
|
||||
{
|
||||
.control
|
||||
{
|
||||
background: $checkRadioSelectedBackground;
|
||||
|
||||
.inner
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.disabled
|
||||
{
|
||||
cursor: not-allowed;
|
||||
|
||||
.label
|
||||
{
|
||||
color: $inputDisabledTextColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.radio
|
||||
{
|
||||
.control, .control .inner
|
||||
{
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.control .inner
|
||||
{
|
||||
color: black;
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
left: 4px;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
&.checked .control .inner
|
||||
{
|
||||
background: #cccccc;
|
||||
box-shadow: 0 1px rgba(0,0,0,0.5);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.check
|
||||
{
|
||||
.control .inner
|
||||
{
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
left: 4px;
|
||||
width: 6px;
|
||||
height: 3px;
|
||||
}
|
||||
|
||||
&.checked .control .inner
|
||||
{
|
||||
border: solid rgba(255,255,255,0.8);
|
||||
border-width: 0 0 2px 2px;
|
||||
transform: rotate(-45deg);
|
||||
box-shadow: -1px 0 rgba(0,0,0,0.2), 0 1px rgba(0,0,0,0.5)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.form-control
|
||||
{
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
|
||||
input[type=text], input[type=number], input[type=password], textarea
|
||||
{
|
||||
@extend %inset;
|
||||
background: $inputBackground;
|
||||
color: $inputTextColor;
|
||||
padding: .5em;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
select
|
||||
{
|
||||
@extend %outset;
|
||||
background: $selectBackground;
|
||||
color: $inputTextColor;
|
||||
font-family: 'Verdana', 'Arial', sans-serif;
|
||||
padding: .5em;
|
||||
}
|
||||
|
||||
input[type=range]
|
||||
{
|
||||
margin-top: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
h1
|
||||
{
|
||||
font-size: 2rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
h2
|
||||
{
|
||||
color: #c0c0c0;
|
||||
font-size: 1.2rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
h3
|
||||
{
|
||||
@extend %outset;
|
||||
color: $sectionHeaderTextColor;
|
||||
background: $sectionHeaderBackground;
|
||||
font-size: 1.2rem;
|
||||
padding: .5rem;
|
||||
}
|
||||
|
||||
h4
|
||||
{
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
|
||||
input[disabled]
|
||||
{
|
||||
cursor: not-allowed;
|
||||
color: $inputDisabledTextColor;
|
||||
background: $inputDisabledBackground;
|
||||
}
|
||||
|
||||
label
|
||||
{
|
||||
display: block;
|
||||
margin-top: .5em;
|
||||
margin-bottom: .5em;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.label-inline
|
||||
{
|
||||
margin-right: 2rem;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@media #{$mediumScreen}
|
||||
{
|
||||
.horizontal
|
||||
{
|
||||
clear: both;
|
||||
|
||||
label
|
||||
{
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
input[type=text], input[type=number], input[type=password], textarea
|
||||
{
|
||||
display: inline-block;
|
||||
float: right;
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
&:after
|
||||
{
|
||||
clear: both;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.hint
|
||||
{
|
||||
display: block;
|
||||
font-size: 8pt;
|
||||
color: #808080;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
|
||||
.loading
|
||||
{
|
||||
margin-top: 3rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
.suboptions
|
||||
{
|
||||
margin-left: 5rem;
|
||||
}
|
||||
|
||||
|
||||
.buttons
|
||||
{
|
||||
clear: both;
|
||||
text-align: center;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
|
||||
.sliders
|
||||
{
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
|
||||
.slidercontainer
|
||||
{
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
|
||||
|
||||
$sliderRedThumbColor: #ce3636;
|
||||
$sliderGreenThumbColor: #32b732;
|
||||
$sliderBlueThumbColor: #4646cc;
|
||||
$sliderWhiteThumbColor: #fcf6cf;
|
||||
|
||||
|
||||
.slider
|
||||
{
|
||||
-webkit-appearance: none;
|
||||
width: 100%;
|
||||
height: $sliderBarSize;
|
||||
border-radius: $sliderBarSize / 2;
|
||||
background: $sliderBarColor;
|
||||
outline: none;
|
||||
|
||||
&::-webkit-slider-thumb
|
||||
{
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
width: $sliderThumbSize;
|
||||
height: $sliderThumbSize;
|
||||
border-radius: 50%;
|
||||
background: $sliderThumbColor;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&::-moz-range-thumb
|
||||
{
|
||||
width: $sliderThumbSize;
|
||||
height: $sliderThumbSize;
|
||||
border-radius: 50%;
|
||||
background: $sliderThumbColor;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
||||
&.red
|
||||
{
|
||||
&::-webkit-slider-thumb { background: $sliderRedThumbColor; }
|
||||
&::-moz-range-thumb { background: $sliderRedThumbColor; }
|
||||
}
|
||||
|
||||
&.green
|
||||
{
|
||||
&::-webkit-slider-thumb { background: $sliderGreenThumbColor; }
|
||||
&::-moz-range-thumb { background: $sliderGreenThumbColor; }
|
||||
}
|
||||
|
||||
&.blue
|
||||
{
|
||||
&::-webkit-slider-thumb { background: $sliderBlueThumbColor; }
|
||||
&::-moz-range-thumb { background: $sliderBlueThumbColor; }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.warning
|
||||
{
|
||||
@extend %outset;
|
||||
background: #973a38;
|
||||
padding: .5em;
|
||||
margin-bottom: 2rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
|
||||
.nodata
|
||||
{
|
||||
color: #808080;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
.clear
|
||||
{
|
||||
clear: both;
|
||||
}
|
||||
|
||||
|
||||
.panel
|
||||
{
|
||||
margin-bottom: 2rem;
|
||||
padding: 0;
|
||||
|
||||
.panel-header
|
||||
{
|
||||
@extend %outset;
|
||||
border-radius: 3px 3px 0 0;
|
||||
border-bottom-width: 0;
|
||||
padding: .5em;
|
||||
|
||||
label {
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
background: $panelHeaderBackground;
|
||||
color: $panelHeaderTextColor;
|
||||
|
||||
.actions
|
||||
{
|
||||
float: right;
|
||||
}
|
||||
|
||||
a, .label
|
||||
{
|
||||
color: $panelHeaderLinkColor;
|
||||
}
|
||||
}
|
||||
|
||||
.panel-body
|
||||
{
|
||||
@extend %outset;
|
||||
border-radius: 0 0 3px 3px;
|
||||
|
||||
background: $panelBodyBackground;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
|
||||
&.active
|
||||
{
|
||||
.panel-header
|
||||
{
|
||||
background: $panelActiveHeaderBackground;
|
||||
color: $panelActiveHeaderTextColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.inline
|
||||
{
|
||||
display: inline-block;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.fade-enter-active, .fade-leave-active
|
||||
{
|
||||
transition: opacity .5s;
|
||||
}
|
||||
|
||||
.fade-enter, .fade-leave-to
|
||||
{
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
|
||||
.range
|
||||
{
|
||||
clear: both;
|
||||
|
||||
.start
|
||||
{
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 49%;
|
||||
|
||||
.slidercontainer
|
||||
{
|
||||
margin-right: 4em;
|
||||
}
|
||||
|
||||
.value
|
||||
{
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 1.5rem;
|
||||
color: $sliderValueColor;
|
||||
}
|
||||
}
|
||||
|
||||
.end
|
||||
{
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
float: right;
|
||||
width: 50%;
|
||||
|
||||
.slidercontainer
|
||||
{
|
||||
margin-left: 4em;
|
||||
}
|
||||
|
||||
.value
|
||||
{
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 1.5rem;
|
||||
color: $sliderValueColor;
|
||||
}
|
||||
}
|
||||
|
||||
&:after
|
||||
{
|
||||
clear: both;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.resetReason
|
||||
{
|
||||
margin-left: 2em;
|
||||
}
|
|
@ -0,0 +1,987 @@
|
|||
<template>
|
||||
<div id="app">
|
||||
<div class="notificationContainer">
|
||||
<div class="notification" :class="{ error: notification != null && notification.isError }" 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>{{ $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">
|
||||
<LoadingIndicator></LoadingIndicator>
|
||||
</div>
|
||||
|
||||
<div v-else>
|
||||
<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">
|
||||
<router-link to="/" class="button" active-class="active" :exact="true">{{ $t('status.tabTitle') }}</router-link><router-link to="/connection" class="button" active-class="active">{{ $t('connection.tabTitle') }}</router-link><router-link to="/system" class="button" active-class="active">{{ $t('system.tabTitle') }}</router-link>
|
||||
</div>
|
||||
|
||||
|
||||
<router-view/>
|
||||
|
||||
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<div class="version">
|
||||
{{ $t('copyright') }}<br>
|
||||
{{ $t('firmwareVersion') }}{{ status.version || '...' }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios'
|
||||
import LoadingIndicator from '@/components/loadingIndicator.vue';
|
||||
import BaseVM from '@/BaseVM';
|
||||
|
||||
export default {
|
||||
mixins: [BaseVM],
|
||||
|
||||
components: {
|
||||
LoadingIndicator
|
||||
},
|
||||
|
||||
data()
|
||||
{
|
||||
return {
|
||||
loading: true,
|
||||
|
||||
status: {
|
||||
systemID: null,
|
||||
version: null,
|
||||
resetReason: null,
|
||||
stackTrace: false
|
||||
},
|
||||
|
||||
wifiStatus: {
|
||||
ap: {
|
||||
enabled: false,
|
||||
ip: '0.0.0.0'
|
||||
},
|
||||
station: {
|
||||
enabled: false,
|
||||
status: 0,
|
||||
ip: '0.0.0.0'
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
mounted()
|
||||
{
|
||||
const self = this;
|
||||
|
||||
self.updateStatus()
|
||||
.then(() =>
|
||||
{
|
||||
self.updateWiFiStatus()
|
||||
.then(() =>
|
||||
{
|
||||
self.loading = false;
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
computed: {
|
||||
notification() { return this.$store.state.notification; },
|
||||
|
||||
hasResetError()
|
||||
{
|
||||
/*
|
||||
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 won’t change
|
||||
REASON_SOFT_WDT_RST = 3 software watch dog reset, GPIO status won’t change
|
||||
REASON_SOFT_RESTART = 4 software restart ,system_restart , GPIO status won’t change
|
||||
REASON_DEEP_SLEEP_AWAKE = 5 wake up from deep-sleep
|
||||
REASON_EXT_SYS_RST = 6 system reset
|
||||
*/
|
||||
return (this.status.resetReason === 1 ||
|
||||
this.status.resetReason === 2 ||
|
||||
this.status.resetReason === 3 ||
|
||||
this.status.stackTrace);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
methods: {
|
||||
getWiFiStationStatus()
|
||||
{
|
||||
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()
|
||||
{
|
||||
if (!this.wifiStatus.station.enabled)
|
||||
return this.$i18n.t('wifiStatus.stationmode.disabled');
|
||||
|
||||
switch (this.wifiStatus.station.status)
|
||||
{
|
||||
case 0: // WL_IDLE_STATUS
|
||||
return this.$i18n.t('wifiStatus.stationmode.idle');
|
||||
|
||||
case 1: // WL_NO_SSID_AVAIL
|
||||
return this.$i18n.t('wifiStatus.stationmode.noSSID');
|
||||
|
||||
case 2: // WL_SCAN_COMPLETED
|
||||
return this.$i18n.t('wifiStatus.stationmode.scanCompleted');
|
||||
|
||||
case 3: // WL_CONNECTED
|
||||
return this.wifiStatus.station.ip;
|
||||
|
||||
case 4: // WL_CONNECT_FAILED
|
||||
return this.$i18n.t('wifiStatus.stationmode.connectFailed');
|
||||
|
||||
case 5: // WL_CONNECTION_LOST
|
||||
return this.$i18n.t('wifiStatus.stationmode.connectionLost');
|
||||
|
||||
case 6: // WL_DISCONNECTED
|
||||
default:
|
||||
return this.$i18n.t('wifiStatus.stationmode.disconnected');
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
updateStatus()
|
||||
{
|
||||
const self = this;
|
||||
|
||||
return axios.get('/api/status', { retry: 10, retryDelay: 1000 })
|
||||
.then(response =>
|
||||
{
|
||||
if (typeof response.data == 'object')
|
||||
self.status = response.data;
|
||||
})
|
||||
.catch(e => self.handleAPIError('error.loadStatus', e));
|
||||
},
|
||||
|
||||
|
||||
updateWiFiStatus()
|
||||
{
|
||||
const self = this;
|
||||
|
||||
return new Promise((resolve, reject) =>
|
||||
{
|
||||
if (!self.saving)
|
||||
{
|
||||
axios.get('/api/connection/status', { retry: 10, retryDelay: 1000 })
|
||||
.then(response =>
|
||||
{
|
||||
if (typeof response.data == 'object')
|
||||
self.wifiStatus = response.data;
|
||||
})
|
||||
.catch(e =>
|
||||
{
|
||||
self.handleAPIError('error.updateWiFiStatus', e);
|
||||
reject(e);
|
||||
})
|
||||
.then(function()
|
||||
{
|
||||
setTimeout(self.updateWiFiStatus, 5000);
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
setTimeout(self.updateWiFiStatus, 5000);
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
deleteStackTrace()
|
||||
{
|
||||
const self = this;
|
||||
|
||||
return axios.get('/api/stacktrace/delete', { retry: 10, retryDelay: 1000 })
|
||||
.then(response =>
|
||||
{
|
||||
self.status.resetReason = 0;
|
||||
self.status.stackTrace = false;
|
||||
})
|
||||
.catch(e => self.handleAPIError('error.stackTraceDeleteError', e));
|
||||
},
|
||||
|
||||
|
||||
hideNotification()
|
||||
{
|
||||
this.$store.dispatch('hideNotification');
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import "variables.scss";
|
||||
|
||||
// TODO check which parts are app-wide and which should be moved to components/view
|
||||
|
||||
html
|
||||
{
|
||||
overscroll-behavior-x: contain;
|
||||
box-sizing: border-box;
|
||||
font-size: 62.5%;
|
||||
}
|
||||
|
||||
*, *:before, *:after
|
||||
{
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
body
|
||||
{
|
||||
overscroll-behavior-x: contain;
|
||||
background-color: rgb(20, 20, 20);
|
||||
color: white;
|
||||
font-family: 'Verdana', 'Arial', sans-serif;
|
||||
font-size: 1.3em;
|
||||
font-weight: 300;
|
||||
letter-spacing: .01em;
|
||||
line-height: 1.3;
|
||||
|
||||
padding-bottom: 3rem;
|
||||
|
||||
@media #{$mediumScreen}
|
||||
{
|
||||
padding-top: 3rem;
|
||||
}
|
||||
}
|
||||
|
||||
a
|
||||
{
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
|
||||
#container
|
||||
{
|
||||
background: $containerBackground;
|
||||
margin-top: 2rem;
|
||||
padding: 1rem;
|
||||
|
||||
box-shadow: 0 0 50px $containerShadowColor;
|
||||
border: solid 1px black;
|
||||
|
||||
@media #{$mediumScreen}
|
||||
{
|
||||
width: 768px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.header
|
||||
{
|
||||
position: relative;
|
||||
|
||||
img
|
||||
{
|
||||
float: left;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.wifistatus
|
||||
{
|
||||
@media #{$smallScreen}
|
||||
{
|
||||
clear: both;
|
||||
margin-top: 3rem;
|
||||
}
|
||||
|
||||
@media #{$mediumScreen}
|
||||
{
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.indicator
|
||||
{
|
||||
display: inline-block;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
border-radius: 50%;
|
||||
margin-right: 0.5rem;
|
||||
|
||||
&[data-status=connected] { background-color: #339966; }
|
||||
&[data-status=disconnected] { border: solid 1px #808080; }
|
||||
&[data-status=connecting] { background-color: #ff9933; }
|
||||
&[data-status=error] { background-color: #cc0000; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
%outset
|
||||
{
|
||||
border: 1px solid #111111;
|
||||
border-radius: 3px;
|
||||
box-shadow: inset 0 1px rgba(255,255,255,0.1), inset 0 -1px 3px rgba(0,0,0,0.3), inset 0 0 0 1px rgba(255,255,255,0.08), 0 1px 2px rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
|
||||
%inset
|
||||
{
|
||||
border: 1px solid #111111;
|
||||
border-color: black #111111 #111111;
|
||||
box-shadow: inset 0 1px 2px rgba(0,0,0,0.25),0 1px rgba(255,255,255,0.08);
|
||||
}
|
||||
|
||||
|
||||
button, input
|
||||
{
|
||||
font-family: 'Verdana', 'Arial', sans-serif;
|
||||
}
|
||||
|
||||
|
||||
@mixin removeSafariStyling
|
||||
{
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
}
|
||||
|
||||
input
|
||||
{
|
||||
@include removeSafariStyling;
|
||||
}
|
||||
|
||||
button, .button, input[type=submit]
|
||||
{
|
||||
@extend %outset;
|
||||
|
||||
display: inline-block;
|
||||
padding: 0 12px;
|
||||
color: $buttonTextColor;
|
||||
background: $buttonBackground;
|
||||
cursor: pointer;
|
||||
line-height: 3rem;
|
||||
|
||||
&:hover, &:focus, &.focus
|
||||
{
|
||||
color: $buttonHoverTextColor;
|
||||
background: $buttonHoverBackground;
|
||||
outline: none
|
||||
}
|
||||
|
||||
&:active, &.active
|
||||
{
|
||||
@extend %inset;
|
||||
|
||||
color: $buttonActiveTextColor;
|
||||
background: $buttonActiveBackground;
|
||||
}
|
||||
}
|
||||
|
||||
input[type=submit], .button-primary
|
||||
{
|
||||
background: $buttonPrimaryBackground;
|
||||
|
||||
&:hover, &:focus, &.focus
|
||||
{
|
||||
background: $buttonPrimaryHoverBackground;
|
||||
}
|
||||
}
|
||||
|
||||
a.button
|
||||
{
|
||||
text-decoration: none
|
||||
}
|
||||
|
||||
|
||||
.navigation
|
||||
{
|
||||
clear: both;
|
||||
margin-top: 3rem;
|
||||
}
|
||||
|
||||
.tabs
|
||||
{
|
||||
&>.button
|
||||
{
|
||||
margin-left: -1px;
|
||||
border-radius: 0;
|
||||
|
||||
&:first-child
|
||||
{
|
||||
margin-left: 0;
|
||||
border-radius: 3px 0 0 3px
|
||||
}
|
||||
|
||||
&:last-child
|
||||
{
|
||||
border-radius: 0 3px 3px 0
|
||||
}
|
||||
|
||||
&:focus
|
||||
{
|
||||
position: relative;
|
||||
z-index: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.version
|
||||
{
|
||||
color: $versionTextColor;
|
||||
font-size: 8pt;
|
||||
text-align: center;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
|
||||
.notificationContainer
|
||||
{
|
||||
position: fixed;
|
||||
top: 2rem;
|
||||
z-index: 666;
|
||||
|
||||
@media #{$mediumScreen}
|
||||
{
|
||||
width: 512px;
|
||||
left: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.notification
|
||||
{
|
||||
@extend %outset;
|
||||
|
||||
background: $notificationBackground;
|
||||
/* border: solid 1px $notificationBorderColor;*/
|
||||
box-shadow: 0 0 10px black;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
padding: .5em;
|
||||
margin-bottom: 2rem;
|
||||
|
||||
position: relative;
|
||||
|
||||
@media #{$mediumScreen}
|
||||
{
|
||||
left: -50%;
|
||||
}
|
||||
|
||||
.message
|
||||
{
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
|
||||
&.error
|
||||
{
|
||||
background: $notificationErrorBackground;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.check, .radio
|
||||
{
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
white-space: nowrap;
|
||||
margin-top: .5em;
|
||||
margin-bottom: .5em;
|
||||
|
||||
.control
|
||||
{
|
||||
@extend %outset;
|
||||
background: $checkRadioBackground;
|
||||
display: inline-block;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.label
|
||||
{
|
||||
display: inline-block;
|
||||
margin-left: .5em;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
&.checked
|
||||
{
|
||||
.control
|
||||
{
|
||||
background: $checkRadioSelectedBackground;
|
||||
|
||||
.inner
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.disabled
|
||||
{
|
||||
cursor: not-allowed;
|
||||
|
||||
.label
|
||||
{
|
||||
color: $inputDisabledTextColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.radio
|
||||
{
|
||||
.control, .control .inner
|
||||
{
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.control .inner
|
||||
{
|
||||
color: black;
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
left: 4px;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
&.checked .control .inner
|
||||
{
|
||||
background: #cccccc;
|
||||
box-shadow: 0 1px rgba(0,0,0,0.5);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.check
|
||||
{
|
||||
.control .inner
|
||||
{
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
left: 4px;
|
||||
width: 6px;
|
||||
height: 3px;
|
||||
}
|
||||
|
||||
&.checked .control .inner
|
||||
{
|
||||
border: solid rgba(255,255,255,0.8);
|
||||
border-width: 0 0 2px 2px;
|
||||
transform: rotate(-45deg);
|
||||
box-shadow: -1px 0 rgba(0,0,0,0.2), 0 1px rgba(0,0,0,0.5)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.form-control
|
||||
{
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
|
||||
input[type=text], input[type=number], input[type=password], textarea
|
||||
{
|
||||
@extend %inset;
|
||||
background: $inputBackground;
|
||||
color: $inputTextColor;
|
||||
padding: .5em;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
select
|
||||
{
|
||||
@extend %outset;
|
||||
background: $selectBackground;
|
||||
color: $inputTextColor;
|
||||
font-family: 'Verdana', 'Arial', sans-serif;
|
||||
padding: .5em;
|
||||
}
|
||||
|
||||
input[type=range]
|
||||
{
|
||||
margin-top: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
h1
|
||||
{
|
||||
font-size: 2rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
h2
|
||||
{
|
||||
color: #c0c0c0;
|
||||
font-size: 1.2rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
h3
|
||||
{
|
||||
@extend %outset;
|
||||
color: $sectionHeaderTextColor;
|
||||
background: $sectionHeaderBackground;
|
||||
font-size: 1.2rem;
|
||||
padding: .5rem;
|
||||
}
|
||||
|
||||
h4
|
||||
{
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
|
||||
input[disabled]
|
||||
{
|
||||
cursor: not-allowed;
|
||||
color: $inputDisabledTextColor;
|
||||
background: $inputDisabledBackground;
|
||||
}
|
||||
|
||||
label
|
||||
{
|
||||
display: block;
|
||||
margin-top: .5em;
|
||||
margin-bottom: .5em;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.label-inline
|
||||
{
|
||||
margin-right: 2rem;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@media #{$mediumScreen}
|
||||
{
|
||||
.horizontal
|
||||
{
|
||||
clear: both;
|
||||
|
||||
label
|
||||
{
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
input[type=text], input[type=number], input[type=password], textarea
|
||||
{
|
||||
display: inline-block;
|
||||
float: right;
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
&:after
|
||||
{
|
||||
clear: both;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.hint
|
||||
{
|
||||
display: block;
|
||||
font-size: 8pt;
|
||||
color: #808080;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
|
||||
.loading
|
||||
{
|
||||
margin-top: 3rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
.suboptions
|
||||
{
|
||||
margin-left: 5rem;
|
||||
}
|
||||
|
||||
|
||||
.buttons
|
||||
{
|
||||
clear: both;
|
||||
text-align: center;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
|
||||
.sliders
|
||||
{
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
|
||||
.slidercontainer
|
||||
{
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
|
||||
|
||||
$sliderRedThumbColor: #ce3636;
|
||||
$sliderGreenThumbColor: #32b732;
|
||||
$sliderBlueThumbColor: #4646cc;
|
||||
$sliderWhiteThumbColor: #fcf6cf;
|
||||
|
||||
|
||||
.slider
|
||||
{
|
||||
-webkit-appearance: none;
|
||||
width: 100%;
|
||||
height: $sliderBarSize;
|
||||
border-radius: $sliderBarSize / 2;
|
||||
background: $sliderBarColor;
|
||||
outline: none;
|
||||
|
||||
&::-webkit-slider-thumb
|
||||
{
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
width: $sliderThumbSize;
|
||||
height: $sliderThumbSize;
|
||||
border-radius: 50%;
|
||||
background: $sliderThumbColor;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&::-moz-range-thumb
|
||||
{
|
||||
width: $sliderThumbSize;
|
||||
height: $sliderThumbSize;
|
||||
border-radius: 50%;
|
||||
background: $sliderThumbColor;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
||||
&.red
|
||||
{
|
||||
&::-webkit-slider-thumb { background: $sliderRedThumbColor; }
|
||||
&::-moz-range-thumb { background: $sliderRedThumbColor; }
|
||||
}
|
||||
|
||||
&.green
|
||||
{
|
||||
&::-webkit-slider-thumb { background: $sliderGreenThumbColor; }
|
||||
&::-moz-range-thumb { background: $sliderGreenThumbColor; }
|
||||
}
|
||||
|
||||
&.blue
|
||||
{
|
||||
&::-webkit-slider-thumb { background: $sliderBlueThumbColor; }
|
||||
&::-moz-range-thumb { background: $sliderBlueThumbColor; }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.warning
|
||||
{
|
||||
@extend %outset;
|
||||
background: #973a38;
|
||||
padding: .5em;
|
||||
margin-bottom: 2rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
|
||||
.nodata
|
||||
{
|
||||
color: #808080;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
.clear
|
||||
{
|
||||
clear: both;
|
||||
}
|
||||
|
||||
|
||||
.panel
|
||||
{
|
||||
margin-bottom: 2rem;
|
||||
padding: 0;
|
||||
|
||||
.panel-header
|
||||
{
|
||||
@extend %outset;
|
||||
border-radius: 3px 3px 0 0;
|
||||
border-bottom-width: 0;
|
||||
padding: .5em;
|
||||
|
||||
label {
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
background: $panelHeaderBackground;
|
||||
color: $panelHeaderTextColor;
|
||||
|
||||
.actions
|
||||
{
|
||||
float: right;
|
||||
}
|
||||
|
||||
a, .label
|
||||
{
|
||||
color: $panelHeaderLinkColor;
|
||||
}
|
||||
}
|
||||
|
||||
.panel-body
|
||||
{
|
||||
@extend %outset;
|
||||
border-radius: 0 0 3px 3px;
|
||||
|
||||
background: $panelBodyBackground;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
|
||||
&.active
|
||||
{
|
||||
.panel-header
|
||||
{
|
||||
background: $panelActiveHeaderBackground;
|
||||
color: $panelActiveHeaderTextColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.inline
|
||||
{
|
||||
display: inline-block;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.fade-enter-active, .fade-leave-active
|
||||
{
|
||||
transition: opacity .5s;
|
||||
}
|
||||
|
||||
.fade-enter, .fade-leave-to
|
||||
{
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
|
||||
.range
|
||||
{
|
||||
clear: both;
|
||||
|
||||
.start
|
||||
{
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 49%;
|
||||
|
||||
.slidercontainer
|
||||
{
|
||||
margin-right: 4em;
|
||||
}
|
||||
|
||||
.value
|
||||
{
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 1.5rem;
|
||||
color: $sliderValueColor;
|
||||
}
|
||||
}
|
||||
|
||||
.end
|
||||
{
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
float: right;
|
||||
width: 50%;
|
||||
|
||||
.slidercontainer
|
||||
{
|
||||
margin-left: 4em;
|
||||
}
|
||||
|
||||
.value
|
||||
{
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 1.5rem;
|
||||
color: $sliderValueColor;
|
||||
}
|
||||
}
|
||||
|
||||
&:after
|
||||
{
|
||||
clear: both;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.resetReason
|
||||
{
|
||||
margin-left: 2em;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,28 @@
|
|||
export default {
|
||||
computed: {
|
||||
saving() { return this.$store.state.saving; }
|
||||
},
|
||||
|
||||
|
||||
methods: {
|
||||
setSaving(value)
|
||||
{
|
||||
self.$store.commit('saving', value);
|
||||
},
|
||||
|
||||
|
||||
showNotification(message, isError)
|
||||
{
|
||||
this.$store.dispatch('showNotification', {
|
||||
message: message,
|
||||
isError: isError || false
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
handleAPIError(messageId, error)
|
||||
{
|
||||
this.$store.dispatch('notifyAPIError', { message: this.$i18n.t(messageId), error });
|
||||
}
|
||||
}
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 6.7 KiB |
|
@ -0,0 +1,44 @@
|
|||
<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>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
title: String,
|
||||
value: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
handleClick()
|
||||
{
|
||||
if (this.disabled)
|
||||
return;
|
||||
|
||||
this.value = !this.value;
|
||||
this.$emit('input', this.value);
|
||||
},
|
||||
|
||||
handleKeyDown(event)
|
||||
{
|
||||
if (event.keyCode == 32)
|
||||
{
|
||||
this.handleClick();
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,43 @@
|
|||
<template>
|
||||
<span>
|
||||
{{ $t(messageId || 'loading') }} {{ indicator }}
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
messageId: null
|
||||
},
|
||||
|
||||
data()
|
||||
{
|
||||
return {
|
||||
stage: 0,
|
||||
indicator: '|'
|
||||
};
|
||||
},
|
||||
|
||||
created()
|
||||
{
|
||||
const self = this;
|
||||
|
||||
self.timer = setInterval(() =>
|
||||
{
|
||||
self.stage++;
|
||||
switch (self.stage)
|
||||
{
|
||||
case 1: self.indicator = '/'; break;
|
||||
case 2: self.indicator = '-'; break;
|
||||
case 3: self.indicator = '\\'; break;
|
||||
case 4: self.indicator = '|'; self.stage = 0; break;
|
||||
}
|
||||
}, 250);
|
||||
},
|
||||
|
||||
destroyed()
|
||||
{
|
||||
clearInterval(this.timer);
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,42 @@
|
|||
<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>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
title: String,
|
||||
value: null,
|
||||
id: null,
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
handleClick()
|
||||
{
|
||||
if (this.disabled)
|
||||
return;
|
||||
|
||||
this.value = this.id;
|
||||
this.$emit('input', this.value);
|
||||
},
|
||||
|
||||
handleKeyDown(event)
|
||||
{
|
||||
if (event.keyCode == 32)
|
||||
{
|
||||
this.handleClick();
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,56 @@
|
|||
<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>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,104 @@
|
|||
export default {
|
||||
title: 'RGBWifi',
|
||||
systemID: 'System ID: ',
|
||||
firmwareVersion: 'Firmware version: ',
|
||||
copyright: 'Copyright © 2020 Mark van Renswoude',
|
||||
loading: 'Please wait, loading...',
|
||||
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',
|
||||
|
||||
noFileSelected: 'No firmware file selected'
|
||||
},
|
||||
|
||||
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'
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
import en from './en'
|
||||
import nl from './nl'
|
||||
|
||||
export default {
|
||||
en,
|
||||
nl
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
export default {
|
||||
title: 'RGBWifi',
|
||||
systemID: 'Systeem ID: ',
|
||||
firmwareVersion: 'Firmware versie: ',
|
||||
copyright: 'Copyright © 2020 Mark van Renswoude',
|
||||
loading: 'Een ogenblik geduld, bezig met laden...',
|
||||
rebootPending: 'Het systeem wordt opnieuw opgestart, ververse deze pagina nadien',
|
||||
|
||||
applyButton: 'Opslaan',
|
||||
applyButtonSaving: 'Bezig met opslaan...',
|
||||
deviceTime: 'Tijd: ',
|
||||
|
||||
wifiStatus: {
|
||||
accesspoint: {
|
||||
title: 'AP: ',
|
||||
disabled: 'Uitgeschakeld'
|
||||
},
|
||||
|
||||
stationmode: {
|
||||
title: 'WiFi: ',
|
||||
disabled: 'Uitgeschakeld',
|
||||
idle: 'Slaapstand',
|
||||
noSSID: 'SSID niet gevonden',
|
||||
scanCompleted: 'Scan afgerond',
|
||||
connectFailed: 'Kan geen verbinding maken',
|
||||
connectionLost: 'Verbinding verloren',
|
||||
disconnected: 'Niet verbonden'
|
||||
}
|
||||
},
|
||||
|
||||
status: {
|
||||
tabTitle: 'Status',
|
||||
title: 'Huidige status',
|
||||
|
||||
staticOff: 'Uit'
|
||||
},
|
||||
|
||||
connection: {
|
||||
tabTitle: 'Verbinding',
|
||||
title: 'Verbinding configuratie',
|
||||
|
||||
accesspoint: 'Access point inschakelen',
|
||||
accesspointHint: 'Maakt het mogelijk om een directe connectie vanaf een apparaat naar deze RGBWifi module te maken om de module te configureren. De RGBWifi module is te benaderen via http://192.168.1.4/ nadat je connectie hebt gemaakt. Schakel deze optie uit na het configureren, aangezien deze niet beveiligd is. Je kunt deze optie ook inschakelen door op de Access point knop te drukken totdat de LED aan gaat.',
|
||||
|
||||
stationmode: 'Verbinding met WiFi maken',
|
||||
stationmodeHint: 'Verbind deze RGBWifi module aan je eigen WiFi router. Vul hieronder het SSID en wachtwoord in, en configureer eventuel de overige opties.',
|
||||
|
||||
ssid: 'SSID',
|
||||
password: 'Wachtwoord',
|
||||
|
||||
dhcp: 'Gebruik DHCP',
|
||||
dhcpHint: 'Automatisch een IP adres toewijzen aan deze RGBWifi module. Waarschijnlijk wil je deze optie aan laten, tenzij je weet waar je mee bezig bent.',
|
||||
|
||||
ipaddress: 'IP adres',
|
||||
subnetmask: 'Subnet masker',
|
||||
gateway: 'Gateway',
|
||||
hostname: 'Hostnaam',
|
||||
hostnameHint: 'Indien ingevuld is deze module te bereiken op <hostnaam>.local als je apparaat mDNS ondersteund mDNS (op het moment van schrijven ondersteund Android dit niet).',
|
||||
hostnamePlaceholder: 'Standaard: mac adres'
|
||||
},
|
||||
|
||||
system: {
|
||||
tabTitle: 'Systeem',
|
||||
pinsTitle: 'Hardware aansluitingen',
|
||||
ledStripTitle: 'LED strip',
|
||||
firmwareTitle: 'Firmware bijwerken',
|
||||
|
||||
pinLEDAP: 'Access Point status LED pin (+3.3v)',
|
||||
pinLEDSTA: 'WiFi status LED pin (+3.3v)',
|
||||
pinAPButton: 'Access Point inschakelen knop pin (actief laag)',
|
||||
|
||||
ledCount: 'Aantal LEDs op strip',
|
||||
|
||||
noFileSelected: 'Geen firmware bestand geselecteerd'
|
||||
},
|
||||
|
||||
error: {
|
||||
loadStatus: 'Kan systeemstatus niet ophalen',
|
||||
loadConnection: 'Kan verbinding instellingen niet ophalen',
|
||||
loadSystem: 'Kan systeem instellingen niet ophalen',
|
||||
applyConnection: 'Kan verbinding instellingen niet opslaan',
|
||||
applySystem: 'Kan systeem instellingen niet opslaan',
|
||||
updateWiFiStatus: 'Kan WiFi status niet ophalen',
|
||||
uploadFirmware: 'Fout tijdens bijwerken van firmware',
|
||||
|
||||
setColor: 'Kan kleur niet zetten',
|
||||
|
||||
resetError: 'Het systeem is onverwachts herstart. De laatste status is:',
|
||||
resetReason: {
|
||||
0: 'Normaal opgestart',
|
||||
1: 'Reageert niet, herstart door hardware watchdog',
|
||||
2: 'Onafgehandelde fout',
|
||||
3: 'Reageert niet, herstart door software watchdog',
|
||||
4: 'Herstart verzoek door systeem',
|
||||
5: 'Wakker geworden uit diepe slaap',
|
||||
6: 'Systeem gereset'
|
||||
},
|
||||
stackTrace: 'Een stack trace is beschikbaar. Stuur het naar de dichtsbijzijnde ontwikkelaar en/of verwijder het van deze RGBWifi module om dit bericht te verbergen.',
|
||||
stackTraceDownload: 'Downloaden',
|
||||
stackTraceDelete: 'Verbergen',
|
||||
|
||||
stackTraceDeleteError: 'Kan stack trace niet verwijderen'
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
|
@ -0,0 +1,63 @@
|
|||
import Vue from 'vue'
|
||||
import VueI18n from 'vue-i18n'
|
||||
import axios from 'axios'
|
||||
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
import store from './store'
|
||||
|
||||
import messages from './i18n'
|
||||
|
||||
|
||||
|
||||
// Source: https://github.com/axios/axios/issues/164
|
||||
axios.interceptors.response.use(undefined, function axiosRetryInterceptor(err) {
|
||||
const 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
|
||||
const 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.use(VueI18n);
|
||||
|
||||
const i18n = new VueI18n({
|
||||
locale: navigator.language.split('-')[0],
|
||||
fallbackLocale: 'en',
|
||||
messages: messages
|
||||
});
|
||||
|
||||
|
||||
|
||||
Vue.config.productionTip = false;
|
||||
|
||||
new Vue({
|
||||
router,
|
||||
store,
|
||||
i18n,
|
||||
el: '#app',
|
||||
render: h => h(App)
|
||||
});
|
|
@ -0,0 +1,33 @@
|
|||
import Vue from 'vue'
|
||||
import VueRouter from 'vue-router'
|
||||
import Status from '../views/Status.vue'
|
||||
import Connection from '../views/Connection.vue'
|
||||
import System from '../views/System.vue'
|
||||
|
||||
Vue.use(VueRouter)
|
||||
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: '/',
|
||||
name: 'Status',
|
||||
component: Status
|
||||
},
|
||||
{
|
||||
path: '/connection',
|
||||
name: 'Connection',
|
||||
component: Connection
|
||||
},
|
||||
{
|
||||
path: '/system',
|
||||
name: 'System',
|
||||
component: System
|
||||
}
|
||||
]
|
||||
|
||||
const router = new VueRouter({
|
||||
routes
|
||||
})
|
||||
|
||||
|
||||
export default router;
|
|
@ -0,0 +1,80 @@
|
|||
import Vue from 'vue'
|
||||
import Vuex from 'vuex'
|
||||
|
||||
Vue.use(Vuex);
|
||||
|
||||
export default new Vuex.Store({
|
||||
state: {
|
||||
notification: null,
|
||||
notificationTimeout: null,
|
||||
saving: false
|
||||
},
|
||||
|
||||
|
||||
mutations: {
|
||||
saving(state, value)
|
||||
{
|
||||
state.saving = value;
|
||||
},
|
||||
|
||||
|
||||
_setNotification(state, payload)
|
||||
{
|
||||
state.notification = payload.notification;
|
||||
state.notificationTimeout = payload.notificationTimeout;
|
||||
}
|
||||
},
|
||||
|
||||
actions: {
|
||||
showNotification(context, payload)
|
||||
{
|
||||
const self = this;
|
||||
if (context.state.notificationTimeout !== null)
|
||||
clearTimeout(context.state.notificationTimeout);
|
||||
|
||||
const notificationTimeout = setTimeout(() =>
|
||||
{
|
||||
context.dispatch('hideNotification');
|
||||
}, 5000);
|
||||
|
||||
context.commit('_setNotification', {
|
||||
notification: payload,
|
||||
notificationTimeout
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
hideNotification(context)
|
||||
{
|
||||
if (context.state.notificationTimeout !== null)
|
||||
clearTimeout(context.state.notificationTimeout);
|
||||
|
||||
context.commit('_setNotification', {
|
||||
notification: null,
|
||||
notificationTimeout: null
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
notifyAPIError(context, payload)
|
||||
{
|
||||
console.log(payload.error);
|
||||
let errorMessage = '';
|
||||
|
||||
if (payload.error.response)
|
||||
{
|
||||
errorMessage = 'HTTP response code ' + payload.error.response.status;
|
||||
}
|
||||
else if (payload.error.request)
|
||||
{
|
||||
errorMessage = 'No response';
|
||||
}
|
||||
else
|
||||
{
|
||||
errorMessage = payload.error.message;
|
||||
}
|
||||
|
||||
context.dispatch('showNotification', { message: payload.message + '\n\n' + errorMessage, isError: true });
|
||||
}
|
||||
},
|
||||
})
|
|
@ -0,0 +1,120 @@
|
|||
<template>
|
||||
<form @submit.prevent="save">
|
||||
<h3>{{ $t('connection.title') }}</h3>
|
||||
|
||||
<div v-if="connection === null" class="loading">
|
||||
<LoadingIndicator></LoadingIndicator>
|
||||
</div>
|
||||
|
||||
<div v-else>
|
||||
<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>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
import LoadingIndicator from '@/components/loadingIndicator.vue';
|
||||
import check from '@/components/check.vue';
|
||||
import BaseVM from '@/BaseVM';
|
||||
|
||||
export default {
|
||||
mixins: [BaseVM],
|
||||
|
||||
components: {
|
||||
LoadingIndicator,
|
||||
check
|
||||
},
|
||||
|
||||
data()
|
||||
{
|
||||
return {
|
||||
connection: null
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
mounted()
|
||||
{
|
||||
this.load();
|
||||
},
|
||||
|
||||
|
||||
methods: {
|
||||
load()
|
||||
{
|
||||
const self = this;
|
||||
|
||||
return axios.get('/api/connection', { retry: 10, retryDelay: 1000 })
|
||||
.then(response =>
|
||||
{
|
||||
if (typeof response.data == 'object')
|
||||
self.connection = response.data;
|
||||
})
|
||||
.catch(e => self.handleAPIError('error.loadConnection', e));
|
||||
},
|
||||
|
||||
|
||||
save()
|
||||
{
|
||||
const self = this;
|
||||
if (self.saving)
|
||||
return;
|
||||
|
||||
self.setSaving(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(response => {})
|
||||
.catch(e => self.handleAPIError('error.applyConnection', e))
|
||||
.then(() =>
|
||||
{
|
||||
self.setSaving(false);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,139 @@
|
|||
<template>
|
||||
<div>
|
||||
<h3>{{ $t('status.title') }}</h3>
|
||||
|
||||
<div v-if="static === null" class="loading">
|
||||
<LoadingIndicator></LoadingIndicator>
|
||||
</div>
|
||||
|
||||
<div v-else>
|
||||
<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>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
import LoadingIndicator from '@/components/loadingIndicator.vue';
|
||||
import BaseVM from '@/BaseVM';
|
||||
|
||||
export default {
|
||||
mixins: [BaseVM],
|
||||
|
||||
components: {
|
||||
LoadingIndicator
|
||||
},
|
||||
|
||||
data()
|
||||
{
|
||||
return {
|
||||
static: null
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
mounted()
|
||||
{
|
||||
const self = this;
|
||||
|
||||
self.disableSetStatic = false;
|
||||
self.setStaticTimer = false;
|
||||
|
||||
self.load()
|
||||
.then(() =>
|
||||
{
|
||||
self.$watch('static', () =>
|
||||
{
|
||||
self.staticChanged();
|
||||
}, { deep: true });
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
methods: {
|
||||
load()
|
||||
{
|
||||
const self = this;
|
||||
|
||||
// TODO load current settings (no API for it yet)
|
||||
self.static = {
|
||||
r: 0,
|
||||
g: 0,
|
||||
b: 0,
|
||||
w: 0
|
||||
};
|
||||
|
||||
return Promise.resolve(true);
|
||||
},
|
||||
|
||||
|
||||
staticOff()
|
||||
{
|
||||
this.static = {
|
||||
r: 0,
|
||||
g: 0,
|
||||
b: 0,
|
||||
w: 0
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
staticChanged()
|
||||
{
|
||||
const self = this;
|
||||
console.log(self.setStaticTimer);
|
||||
|
||||
if (self.setStaticTimer === false)
|
||||
self.setStaticTimer = setTimeout(() =>
|
||||
{
|
||||
self.setStatic();
|
||||
}, 200);
|
||||
},
|
||||
|
||||
|
||||
setStatic()
|
||||
{
|
||||
const self = this;
|
||||
|
||||
if (self.settingStatic)
|
||||
{
|
||||
self.setStaticTimer = setTimeout(() =>
|
||||
{
|
||||
self.setStatic();
|
||||
}, 200);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
self.settingStatic = true;
|
||||
self.setStaticTimer = false;
|
||||
|
||||
|
||||
axios.get('/api/set/static', { params: this.static })
|
||||
.then(response =>
|
||||
{
|
||||
})
|
||||
.catch(e => self.handleAPIError('error.setColor', e))
|
||||
.then(() =>
|
||||
{
|
||||
self.settingStatic = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,161 @@
|
|||
<template>
|
||||
<div>
|
||||
<h3>{{ $t('system.firmwareTitle') }}</h3>
|
||||
|
||||
<div v-if="system === null" class="loading">
|
||||
<LoadingIndicator></LoadingIndicator>
|
||||
</div>
|
||||
|
||||
<div v-else>
|
||||
<form @submit.prevent="uploadFirmware">
|
||||
<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>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
import LoadingIndicator from '@/components/loadingIndicator.vue';
|
||||
import BaseVM from '@/BaseVM';
|
||||
|
||||
export default {
|
||||
mixins: [BaseVM],
|
||||
|
||||
components: {
|
||||
LoadingIndicator
|
||||
},
|
||||
|
||||
data()
|
||||
{
|
||||
return {
|
||||
system: null,
|
||||
uploadProgress: false
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
mounted()
|
||||
{
|
||||
this.load();
|
||||
},
|
||||
|
||||
|
||||
methods: {
|
||||
load()
|
||||
{
|
||||
const self = this;
|
||||
|
||||
return axios.get('/api/system', { retry: 10, retryDelay: 1000 })
|
||||
.then(response =>
|
||||
{
|
||||
if (typeof response.data == 'object')
|
||||
self.system = response.data;
|
||||
})
|
||||
.catch(e => self.handleAPIError('error.loadSystem', e));
|
||||
},
|
||||
|
||||
|
||||
save()
|
||||
{
|
||||
const self = this;
|
||||
if (self.saving)
|
||||
return;
|
||||
|
||||
self.setSaving(true);
|
||||
|
||||
axios.post('/api/system', self.system, { retry: 10, retryDelay: 1000, headers: { 'Content-Type': 'application/json' } })
|
||||
.then(response =>
|
||||
{
|
||||
self.showNotification(i18n.t('rebootPending'));
|
||||
})
|
||||
.catch(e => self.handleAPIError('error.applySystem', e))
|
||||
.then(() =>
|
||||
{
|
||||
self.setSaving(false);
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
uploadFirmware()
|
||||
{
|
||||
const self = this;
|
||||
if (self.saving) return;
|
||||
|
||||
const fileElement = document.getElementById('firmwareFile');
|
||||
if (fileElement.files.length == 0)
|
||||
{
|
||||
self.showNotification(self.$i18n.t('system.noFileSelected'), true);
|
||||
return;
|
||||
}
|
||||
|
||||
self.saving = true;
|
||||
self.uploadProgress = 0;
|
||||
|
||||
const data = new FormData();
|
||||
|
||||
data.append('file', fileElement.files[0]);
|
||||
|
||||
var config = {
|
||||
timeout: 360000,
|
||||
onUploadProgress: progressEvent =>
|
||||
{
|
||||
self.uploadProgress = Math.round((progressEvent.loaded * 100) / progressEvent.total);
|
||||
}
|
||||
};
|
||||
|
||||
axios.post('/api/firmware', data, config)
|
||||
.then(response =>
|
||||
{
|
||||
self.showNotification(self.$i18n.t('rebootPending'));
|
||||
})
|
||||
.catch(e => self.handleAPIError('error.uploadFirmware', e))
|
||||
.then(() =>
|
||||
{
|
||||
self.uploadProgress = false;
|
||||
self.saving = false;
|
||||
|
||||
document.getElementById('firmware').reset();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,19 @@
|
|||
const { merge } = require('webpack-merge');
|
||||
const config = require('./webpack.config.js');
|
||||
const webpack = require('webpack');
|
||||
const TerserPlugin = require('terser-webpack-plugin');
|
||||
|
||||
|
||||
module.exports = merge(config, {
|
||||
mode: "production",
|
||||
devtool: "#source-map",
|
||||
|
||||
output: {
|
||||
filename: 'bundle.js'
|
||||
},
|
||||
|
||||
optimization: {
|
||||
minimize: true,
|
||||
minimizer: [new TerserPlugin()],
|
||||
}
|
||||
});
|
|
@ -0,0 +1,68 @@
|
|||
'use strict'
|
||||
|
||||
const webpack = require('webpack');
|
||||
const path = require('path');
|
||||
const { VueLoaderPlugin } = require('vue-loader');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
|
||||
module.exports = {
|
||||
mode: 'development',
|
||||
entry: [
|
||||
'./web/src/main.js'
|
||||
],
|
||||
output: {
|
||||
path: path.resolve(__dirname, './web/dist'),
|
||||
filename: 'bundle.js'
|
||||
},
|
||||
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, './web/src'),
|
||||
vue$: 'vue/dist/vue.runtime.esm.js'
|
||||
},
|
||||
},
|
||||
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.vue$/,
|
||||
use: 'vue-loader'
|
||||
},
|
||||
{
|
||||
test: /\.scss$/,
|
||||
use: [
|
||||
'vue-style-loader',
|
||||
{
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
esModule: false
|
||||
}
|
||||
},
|
||||
'sass-loader'
|
||||
]
|
||||
},
|
||||
{
|
||||
test: /\.js$/,
|
||||
loader: 'babel-loader',
|
||||
exclude: file => (
|
||||
/node_modules/.test(file) &&
|
||||
!/\.vue\.js/.test(file)
|
||||
)
|
||||
},
|
||||
{
|
||||
test: /\.(png)?$/,
|
||||
use: [{
|
||||
loader: 'file-loader'
|
||||
}]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
plugins: [
|
||||
new VueLoaderPlugin(),
|
||||
new HtmlWebpackPlugin({
|
||||
title: 'RGBWifi',
|
||||
template: 'web/public/index.html'
|
||||
})
|
||||
]
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
const { merge } = require('webpack-merge');
|
||||
const config = require("./webpack.config.js");
|
||||
const webpack = require("webpack");
|
||||
|
||||
module.exports = merge(config, {
|
||||
mode: 'development',
|
||||
devServer: {
|
||||
historyApiFallback: true,
|
||||
proxy:{
|
||||
'/api': {
|
||||
target: 'http://127.0.0.1:3000'
|
||||
}
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
new webpack.NoEmitOnErrorsPlugin(),
|
||||
]
|
||||
});
|
Loading…
Reference in New Issue