Initial commit
This commit is contained in:
+149
@@ -0,0 +1,149 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
.pnpm-debug.log*
|
||||||
|
|
||||||
|
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||||
|
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
*.lcov
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Bower dependency directory (https://bower.io/)
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# Snowpack dependency directory (https://snowpack.dev/)
|
||||||
|
web_modules/
|
||||||
|
|
||||||
|
# TypeScript cache
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Optional stylelint cache
|
||||||
|
.stylelintcache
|
||||||
|
|
||||||
|
# Microbundle cache
|
||||||
|
.rpt2_cache/
|
||||||
|
.rts2_cache_cjs/
|
||||||
|
.rts2_cache_es/
|
||||||
|
.rts2_cache_umd/
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variable files
|
||||||
|
.env
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
.env.local
|
||||||
|
|
||||||
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
|
.cache
|
||||||
|
.parcel-cache
|
||||||
|
|
||||||
|
# Next.js build output
|
||||||
|
.next
|
||||||
|
out
|
||||||
|
|
||||||
|
# Nuxt.js build / generate output
|
||||||
|
.nuxt
|
||||||
|
dist
|
||||||
|
|
||||||
|
# Gatsby files
|
||||||
|
.cache/
|
||||||
|
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||||
|
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||||
|
# public
|
||||||
|
|
||||||
|
# vuepress build output
|
||||||
|
.vuepress/dist
|
||||||
|
|
||||||
|
# vuepress v2.x temp and cache directory
|
||||||
|
.temp
|
||||||
|
.cache
|
||||||
|
|
||||||
|
# vitepress build output
|
||||||
|
**/.vitepress/dist
|
||||||
|
|
||||||
|
# vitepress cache directory
|
||||||
|
**/.vitepress/cache
|
||||||
|
|
||||||
|
# Docusaurus cache and generated files
|
||||||
|
.docusaurus
|
||||||
|
|
||||||
|
# Serverless directories
|
||||||
|
.serverless/
|
||||||
|
|
||||||
|
# FuseBox cache
|
||||||
|
.fusebox/
|
||||||
|
|
||||||
|
# DynamoDB Local files
|
||||||
|
.dynamodb/
|
||||||
|
|
||||||
|
# TernJS port file
|
||||||
|
.tern-port
|
||||||
|
|
||||||
|
# Stores VSCode versions used for testing VSCode extensions
|
||||||
|
.vscode-test
|
||||||
|
|
||||||
|
# yarn v2
|
||||||
|
.yarn/cache
|
||||||
|
.yarn/unplugged
|
||||||
|
.yarn/build-state.yml
|
||||||
|
.yarn/install-state.gz
|
||||||
|
.pnp.*
|
||||||
|
|
||||||
|
# config files
|
||||||
|
app/config/
|
||||||
|
app/logs/
|
||||||
|
|
||||||
|
# database
|
||||||
|
sensors.db
|
||||||
|
|
||||||
|
# temp
|
||||||
|
app/temp
|
||||||
|
|
||||||
|
# storage
|
||||||
|
app/storage
|
||||||
@@ -0,0 +1,204 @@
|
|||||||
|
const logger = require("../utils/logger.util.js");
|
||||||
|
const path = require("path");
|
||||||
|
const fs = require("fs");
|
||||||
|
const uuid = require("uuid");
|
||||||
|
const app_conf = require("../config/app.conf.js");
|
||||||
|
const map_conf = require("../config/map.conf.js");
|
||||||
|
const messages = require("../config/messages.conf.js");
|
||||||
|
|
||||||
|
const map = require("../utils/map.util.js");
|
||||||
|
|
||||||
|
const express = require('express');
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
function errorPageRenderer(res, emoji, message) {
|
||||||
|
res.render("error", {
|
||||||
|
page_title: app_conf.name,
|
||||||
|
css_path: path.join(app_conf.paths.css.web, "stylesheet.css"),
|
||||||
|
js_path: path.join(app_conf.paths.js.web, "error.js"),
|
||||||
|
error_emoji: emoji,
|
||||||
|
error_message: message
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
router.delete("/remove", async (req, res) => {
|
||||||
|
logger.info(`${req.method}: "${req.url}" => ${req.get("User-Agent")}`);
|
||||||
|
|
||||||
|
if (req.query["element"]) {
|
||||||
|
try {
|
||||||
|
fs.unlinkSync(`app/storage/${req.query["element"]}`);
|
||||||
|
res.status(200).send();
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).send();
|
||||||
|
logger.error(error.message);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
res.status(400).send();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post("/upload", async (req, res) => {
|
||||||
|
logger.info(`${req.method}: "${req.url}" => ${req.get("User-Agent")}`);
|
||||||
|
|
||||||
|
if (!req.files || !req.files.file) {
|
||||||
|
res.status(422).send();
|
||||||
|
} else {
|
||||||
|
const uploaded_file = req.files.file;
|
||||||
|
|
||||||
|
let map_name = path.parse(uploaded_file["name"])["name"];
|
||||||
|
if (map.mapExists(uploaded_file["name"])) {
|
||||||
|
map_name += `_${uuid.v4()}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.copyFile(uploaded_file.tempFilePath, `app/storage/${map_name}.csv`, (error) => {
|
||||||
|
if (error) {
|
||||||
|
logger.error(error);
|
||||||
|
res.status(500).send();
|
||||||
|
} else {
|
||||||
|
res.status(201).send();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get("/download", async (req, res) => {
|
||||||
|
logger.info(`${req.method}: "${req.url}" => ${req.get("User-Agent")}`);
|
||||||
|
|
||||||
|
if (req.query["element"]) {
|
||||||
|
res.download(`app/storage/${req.query["element"]}`);
|
||||||
|
} else {
|
||||||
|
res.status(400).send();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post("/add", async (req, res) => {
|
||||||
|
logger.info(`${req.method}: "${req.url}" => ${req.get("User-Agent")}`);
|
||||||
|
|
||||||
|
if (req.body) {
|
||||||
|
await map.addMap(`map_${uuid.v4()}.csv`, req.body["meta"], req.body["throttle"], req.body["motor"]);
|
||||||
|
|
||||||
|
res.status(200).send();
|
||||||
|
} else {
|
||||||
|
res.status(400).send();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post("/edit", async (req, res) => {
|
||||||
|
logger.info(`${req.method}: "${req.url}" => ${req.get("User-Agent")}`);
|
||||||
|
|
||||||
|
if (req.query["element"] && req.body) {
|
||||||
|
try {
|
||||||
|
await map.updateMap(req.query["element"], req.body["meta"], req.body["throttle"], req.body["motor"]);
|
||||||
|
|
||||||
|
res.status(200).send();
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).send();
|
||||||
|
logger.error(error.message);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
res.status(400).send();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get("/add", async (req, res) => {
|
||||||
|
logger.info(`${req.method}: "${req.url}" => ${req.get("User-Agent")}`);
|
||||||
|
|
||||||
|
res.render("add", {
|
||||||
|
page_title: app_conf.name,
|
||||||
|
program: app_conf.program,
|
||||||
|
css_path: path.join(app_conf.paths.css.web, "stylesheet.css"),
|
||||||
|
js_path: path.join(app_conf.paths.js.web, "add.js"),
|
||||||
|
chartjs: app_conf.paths.chartjs.web + "/chart.umd.js",
|
||||||
|
steps: map_conf.steps,
|
||||||
|
max_rpm: map_conf.max_rpm,
|
||||||
|
max_throttle: map_conf.max_throttle
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get("/edit", async (req, res) => {
|
||||||
|
logger.info(`${req.method}: "${req.url}" => ${req.get("User-Agent")}`);
|
||||||
|
|
||||||
|
const requested_map = req.query["element"];
|
||||||
|
if (requested_map) {
|
||||||
|
try {
|
||||||
|
if (map.mapExists(requested_map)) {
|
||||||
|
res.render("edit", {
|
||||||
|
page_title: app_conf.name,
|
||||||
|
program: app_conf.program,
|
||||||
|
css_path: path.join(app_conf.paths.css.web, "stylesheet.css"),
|
||||||
|
js_path: path.join(app_conf.paths.js.web, "edit.js"),
|
||||||
|
chartjs: app_conf.paths.chartjs.web + "/chart.umd.js",
|
||||||
|
map: JSON.stringify(await map.parseMapJson(requested_map, 0)),
|
||||||
|
map_meta: JSON.stringify(await map.parseMapJson(requested_map, 1)),
|
||||||
|
steps: map_conf.steps,
|
||||||
|
max_rpm: map_conf.max_rpm,
|
||||||
|
max_throttle: map_conf.max_throttle
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
errorPageRenderer(res, "🔍❌", messages.not_found);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
errorPageRenderer(res, "🌋💥", messages.internal_server_error);
|
||||||
|
logger.error(error.message);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
errorPageRenderer(res, "✋⛔", messages.bad_request);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get("/", async (req, res) => {
|
||||||
|
logger.info(`${req.method}: "${req.url}" => ${req.get("User-Agent")}`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const maps = await map.getAvailableMaps();
|
||||||
|
|
||||||
|
if (Object.keys(maps).length) {
|
||||||
|
var table_data = "";
|
||||||
|
for (m of Object.keys(maps)) {
|
||||||
|
table_data += `<tr><td>${m}</td>`;
|
||||||
|
if ("name" in maps[m]) {
|
||||||
|
table_data += `<td>${maps[m]["name"]}</td>`;
|
||||||
|
} else {
|
||||||
|
table_data += "<td>-</td>";
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("id" in maps[m]) {
|
||||||
|
table_data += `<td>${maps[m]["id"]}</td>`;
|
||||||
|
} else {
|
||||||
|
table_data += "<td>-</td>";
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("description" in maps[m]) {
|
||||||
|
table_data += `<td>${maps[m]["description"]}</td>`;
|
||||||
|
} else {
|
||||||
|
table_data += "<td>-</td>";
|
||||||
|
}
|
||||||
|
|
||||||
|
table_data += "</tr>";
|
||||||
|
}
|
||||||
|
|
||||||
|
res.render("index", {
|
||||||
|
page_title: app_conf.name,
|
||||||
|
program: app_conf.program,
|
||||||
|
css_path: path.join(app_conf.paths.css.web, "stylesheet.css"),
|
||||||
|
js_path: path.join(app_conf.paths.js.web, "index.js"),
|
||||||
|
table_data: table_data
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
errorPageRenderer(res, "❓", messages.empty_db);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
errorPageRenderer(res, "🌋💥", messages.internal_server_error);
|
||||||
|
logger.error(error.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get("/about", (req, res) => {
|
||||||
|
res.render("about", {
|
||||||
|
page_title: app_conf.name,
|
||||||
|
css_path: path.join(app_conf.paths.css.web, "stylesheet.css"),
|
||||||
|
version: app_conf.version
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
const path = require("path");
|
||||||
|
const app_conf = require("../config/app.conf.js");
|
||||||
|
|
||||||
|
const express = require('express');
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
router.use(app_conf.paths.css.web, express.static(path.join(path.dirname(require.main.filename), app_conf.paths.css.src)));
|
||||||
|
router.use(app_conf.paths.js.web, express.static(path.join(path.dirname(require.main.filename), app_conf.paths.js.src)));
|
||||||
|
router.use(app_conf.paths.chartjs.web, express.static(path.join(path.dirname(require.main.filename), app_conf.paths.chartjs.src)));
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Main endpoint - might implement authentication step here...
|
||||||
|
*/
|
||||||
|
router.get("/", (req, res) => {
|
||||||
|
res.render("auth", {
|
||||||
|
page_title: app_conf.name,
|
||||||
|
|
||||||
|
program: app_conf.program,
|
||||||
|
css_path: path.join(app_conf.paths.css.web, "stylesheet.css"),
|
||||||
|
protocol: app_conf.protocol,
|
||||||
|
host: app_conf.host,
|
||||||
|
port: app_conf.port
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
const winston = require("winston");
|
||||||
|
const app_conf = require("../config/app.conf");
|
||||||
|
const path = require("path");
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Initialize console and file logger
|
||||||
|
*/
|
||||||
|
const logger = winston.createLogger({
|
||||||
|
levels: winston.config.syslog.levels,
|
||||||
|
level: process.env.LOG_LEVEL || app_conf.logger.level,
|
||||||
|
format: winston.format.combine(
|
||||||
|
winston.format.timestamp({
|
||||||
|
format: app_conf.timestamp_format,
|
||||||
|
}),
|
||||||
|
winston.format.printf((info) => `[${info.timestamp}][${info.level}]: ${info.message}`)
|
||||||
|
),
|
||||||
|
transports: [
|
||||||
|
new winston.transports.File({ filename: path.join(path.dirname(require.main.filename), app_conf.logger.error_path), level: "error", }),
|
||||||
|
new winston.transports.File({ filename: path.join(path.dirname(require.main.filename), app_conf.logger.full_path) }),
|
||||||
|
new winston.transports.Console()
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = logger;
|
||||||
@@ -0,0 +1,180 @@
|
|||||||
|
const csv = require("fast-csv");
|
||||||
|
const fs = require("fs");
|
||||||
|
const path = require("path");
|
||||||
|
const { steps } = require("../config/map.conf");
|
||||||
|
|
||||||
|
async function addMap(filename, meta, throttle, motor) {
|
||||||
|
var total = "";
|
||||||
|
var map = [[[]]];
|
||||||
|
|
||||||
|
if (meta) {
|
||||||
|
if ("name" in meta) {
|
||||||
|
total += `#name:${meta["name"]}\n`;
|
||||||
|
}
|
||||||
|
if ("id" in meta) {
|
||||||
|
total += `#id:${meta["id"]}\n`;
|
||||||
|
}
|
||||||
|
if ("description" in meta) {
|
||||||
|
total += `#description:${meta["description"]}\n`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (throttle && motor && ("0" in motor) && ("1" in motor)) {
|
||||||
|
const throttle_values = Object.values(throttle);
|
||||||
|
const current_values = Object.values(motor["0"]);
|
||||||
|
const brake_values = Object.values(motor["1"]);
|
||||||
|
|
||||||
|
for (let i = 0; i <= steps; i++) {
|
||||||
|
map[i] = [current_values[i], brake_values[i], throttle_values[i]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await csv.writeToString(map).then(data => total += data);
|
||||||
|
fs.writeFileSync(`app/storage/${filename}`, total);
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateMap(filename, meta, throttle, motor) {
|
||||||
|
var total = await generateComments(filename, meta);
|
||||||
|
const map = await generateData(filename, throttle, motor);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await csv.writeToString(map).then(data => total += data);
|
||||||
|
fs.writeFileSync(`app/storage/${filename}`, total);
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function generateData(filename, throttle, motor) {
|
||||||
|
const map = await parseMap(filename, 0);
|
||||||
|
|
||||||
|
if (throttle) {
|
||||||
|
const throttle_keys = Object.keys(throttle);
|
||||||
|
for (k of throttle_keys) {
|
||||||
|
map[k][2] = throttle[k];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (motor && ("0" in motor) && ("1" in motor)) {
|
||||||
|
const current_keys = Object.keys(motor["0"]);
|
||||||
|
for (k of current_keys) {
|
||||||
|
map[k][0] = motor["0"][k];
|
||||||
|
}
|
||||||
|
|
||||||
|
const brake_keys = Object.keys(motor["1"]);
|
||||||
|
for (k of brake_keys) {
|
||||||
|
map[k][1] = motor["1"][k];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function generateComments(filename, meta) {
|
||||||
|
const comments = await parseMapJson(filename, 1);
|
||||||
|
var comment_string = "";
|
||||||
|
|
||||||
|
if (meta) {
|
||||||
|
if ("name" in meta) {
|
||||||
|
comments["name"] = meta["name"];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("id" in meta) {
|
||||||
|
comments["id"] = meta["id"];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("description" in meta) {
|
||||||
|
comments["description"] = meta["description"];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (k of Object.keys(comments)) {
|
||||||
|
comment_string += `#${k}:${comments[k]}\n`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return comment_string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapExists(filename) {
|
||||||
|
if (path.extname(filename) != ".csv") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return fs.existsSync(`app/storage/${filename}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// mode: comments 1 or data 0
|
||||||
|
function parseMap(filename, mode) {
|
||||||
|
var result = [];
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (mode) {
|
||||||
|
csv.parseFile(`app/storage/${filename}`)
|
||||||
|
.on('error', error => reject(error))
|
||||||
|
.on('data', row => row[0][0] === "#" ? result.push(row[0].slice(1)) : resolve(result))
|
||||||
|
.on('end', () => resolve(result));
|
||||||
|
} else {
|
||||||
|
csv.parseFile(`app/storage/${filename}`, { comment: "#" })
|
||||||
|
.on('error', error => reject(error))
|
||||||
|
.on('data', row => result.push(row))
|
||||||
|
.on('end', () => resolve(result));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function parseMapJson(filename, mode) {
|
||||||
|
const map = await parseMap(filename, mode);
|
||||||
|
var json_map = {};
|
||||||
|
|
||||||
|
if (mode) {
|
||||||
|
if (map && map.length) {
|
||||||
|
for (const m of map) {
|
||||||
|
json_map[m.slice(0, m.indexOf(":")).toLowerCase()] = m.slice(m.indexOf(":") + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (let i = 0; i < map.length; i++) {
|
||||||
|
json_map[i] = map[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return json_map
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getAvailableMaps() {
|
||||||
|
// path in app_conf
|
||||||
|
var files = fs.readdirSync("app/storage");
|
||||||
|
var available_maps = {};
|
||||||
|
|
||||||
|
for (const file of files) {
|
||||||
|
if (path.extname(file) == ".csv") {
|
||||||
|
available_maps[file] = {};
|
||||||
|
|
||||||
|
try {
|
||||||
|
let meta = await parseMap(file, 1);
|
||||||
|
|
||||||
|
if (meta && meta.length) {
|
||||||
|
for (const m of meta) {
|
||||||
|
available_maps[file][m.slice(0, m.indexOf(":")).toLowerCase()] = m.slice(m.indexOf(":") + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return available_maps;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getAvailableMaps,
|
||||||
|
parseMap,
|
||||||
|
parseMapJson,
|
||||||
|
mapExists,
|
||||||
|
updateMap,
|
||||||
|
addMap
|
||||||
|
};
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>
|
||||||
|
<%= page_title %>
|
||||||
|
</title>
|
||||||
|
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" />
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link
|
||||||
|
href="https://fonts.googleapis.com/css2?family=Ubuntu:ital,wght@0,300;0,400;0,500;0,700;1,300;1,400;1,500;1,700&display=swap"
|
||||||
|
rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="<%= css_path %>">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="control_div" style="display: flex; flex-direction: row; justify-content: space-between;">
|
||||||
|
<label style="font-size: 200%;">CRTelemetry SQLite3</label>
|
||||||
|
</div>
|
||||||
|
<div class="main_div">
|
||||||
|
<center>
|
||||||
|
<label>Version <%= version %></label>
|
||||||
|
<br>
|
||||||
|
<a href="https://github.com/Centaurus-Racing-Team/5G-telemetry" target="_blank"
|
||||||
|
style="font-size: 150%;">GitHub</a>
|
||||||
|
</center>
|
||||||
|
<br>
|
||||||
|
<a href="/app"><span>🢀 </span>Return</a>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>
|
||||||
|
<%= page_title %>
|
||||||
|
</title>
|
||||||
|
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" />
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link
|
||||||
|
href="https://fonts.googleapis.com/css2?family=Ubuntu:ital,wght@0,300;0,400;0,500;0,700;1,300;1,400;1,500;1,700&display=swap"
|
||||||
|
rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="<%= css_path %>">
|
||||||
|
<script src="<%= js_path %>" defer></script>
|
||||||
|
<script src="<%= chartjs %>"></script>
|
||||||
|
<script defer>
|
||||||
|
var steps = '<%= steps %>', max_rpm = '<%= max_rpm %>', max_throttle = '<%= max_throttle %>';
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="control_div" style="display: flex; flex-direction: row; justify-content: space-between;">
|
||||||
|
<label style="font-size: 200%;">
|
||||||
|
<%= page_title%>
|
||||||
|
<%= program %>
|
||||||
|
</label>
|
||||||
|
<div style="display: flex;align-items: center;">
|
||||||
|
<label id="download" style="cursor: pointer; margin-right: 10px;"><span>🡇
|
||||||
|
</span>Download</label>
|
||||||
|
<label id="remove" style="cursor: pointer; margin-left: 10px; margin-right: 10px;"><span>-
|
||||||
|
</span>Remove</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="main_div">
|
||||||
|
<center><label>Add record</label></center>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>id</th>
|
||||||
|
<th>Description</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><input id="map_name" type="text" placeholder="map_name"
|
||||||
|
style="width: 100%; height: 100%; box-sizing: border-box; font-size: 16px;"></td>
|
||||||
|
<td><input id="map_id" type="text" placeholder="map_id"
|
||||||
|
style="width: 100%; height: 100%; box-sizing: border-box; font-size: 16px;"></td>
|
||||||
|
<td><input id="map_description" type="text" placeholder="map_description"
|
||||||
|
style="width: 100%; height: 100%; box-sizing: border-box; font-size: 16px;"></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<hr class="solid">
|
||||||
|
<div>
|
||||||
|
<div style="height: 50vh; width: 100%;"><canvas id="throttle_plot"></canvas></div>
|
||||||
|
<table id="throttle_table" style="text-align: center;"></table>
|
||||||
|
</div>
|
||||||
|
<hr class="solid">
|
||||||
|
<div>
|
||||||
|
<div style="height: 50vh; width: 100%;"><canvas id="motor_plot"></canvas></div>
|
||||||
|
<table id="motor_table" style="text-align: center;"></table>
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; flex-direction: row; justify-content: space-between;">
|
||||||
|
<a href="/app"><span>🢀 </span>Return</a>
|
||||||
|
<label id="commit" style="cursor: pointer;"><span>✅ </span>Commit</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="about_div">
|
||||||
|
<center><a href="/app/about">About</label></center>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>
|
||||||
|
<%= page_title %>
|
||||||
|
</title>
|
||||||
|
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" />
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link
|
||||||
|
href="https://fonts.googleapis.com/css2?family=Ubuntu:ital,wght@0,300;0,400;0,500;0,700;1,300;1,400;1,500;1,700&display=swap"
|
||||||
|
rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="<%= css_path %>">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="control_div">
|
||||||
|
<label
|
||||||
|
style='font-family: "Ubuntu", sans-serif; font-weight: 500; font-style: normal; font-size: 200%;'>Centaurus
|
||||||
|
Racing Team</label>
|
||||||
|
</div>
|
||||||
|
<div class="main_div"
|
||||||
|
style="display: flex; flex-direction: column; justify-content: space-between; font-size: 150%; margin-top: 15px; margin-bottom: 15px;">
|
||||||
|
<div style="display: flex; flex-direction: row; justify-content: space-evenly;">
|
||||||
|
<label><%= page_title %> <%= program %></label>
|
||||||
|
<a href="<%= protocol %>://<%= host %>:<%= port %>/app">Enter</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>
|
||||||
|
<%= page_title %>
|
||||||
|
</title>
|
||||||
|
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" />
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link
|
||||||
|
href="https://fonts.googleapis.com/css2?family=Ubuntu:ital,wght@0,300;0,400;0,500;0,700;1,300;1,400;1,500;1,700&display=swap"
|
||||||
|
rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="<%= css_path %>">
|
||||||
|
<script type="module" src="<%= js_path %>" defer></script>
|
||||||
|
<script src="<%= chartjs %>"></script>
|
||||||
|
<script defer>
|
||||||
|
var map = '<%- map %>', map_meta = '<%- map_meta %>', steps = '<%= steps %>', max_rpm = '<%= max_rpm %>', max_throttle = '<%= max_throttle %>';
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="control_div" style="display: flex; flex-direction: row; justify-content: space-between;">
|
||||||
|
<label style="font-size: 200%;">
|
||||||
|
<%= page_title%>
|
||||||
|
<%= program %>
|
||||||
|
</label>
|
||||||
|
<div style="display: flex;align-items: center;">
|
||||||
|
<label id="download" style="cursor: pointer; margin-right: 10px;"><span>🡇
|
||||||
|
</span>Download</label>
|
||||||
|
<label id="remove" style="cursor: pointer; margin-left: 10px; margin-right: 10px;"><span>-
|
||||||
|
</span>Remove</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="main_div">
|
||||||
|
<center><label>Edit record</label></center>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>id</th>
|
||||||
|
<th>Description</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><input id="map_name" type="text" placeholder="map_name" style="width: 100%; height: 100%; box-sizing: border-box; font-size: 16px;"></td>
|
||||||
|
<td><input id="map_id" type="text" placeholder="map_id" style="width: 100%; height: 100%; box-sizing: border-box; font-size: 16px;"></td>
|
||||||
|
<td><input id="map_description" type="text" placeholder="map_description" style="width: 100%; height: 100%; box-sizing: border-box; font-size: 16px;"></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<hr class="solid">
|
||||||
|
<div>
|
||||||
|
<div style="height: 50vh; width: 100%;"><canvas id="throttle_plot"></canvas></div>
|
||||||
|
<table id="throttle_table" style="text-align: center;"></table>
|
||||||
|
</div>
|
||||||
|
<hr class="solid">
|
||||||
|
<div>
|
||||||
|
<div style="height: 50vh; width: 100%;"><canvas id="motor_plot"></canvas></div>
|
||||||
|
<table id="motor_table" style="text-align: center;"></table>
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; flex-direction: row; justify-content: space-between;">
|
||||||
|
<a href="/app"><span>🢀 </span>Return</a>
|
||||||
|
<label id="commit" style="cursor: pointer;"><span>✅ </span>Commit</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="about_div">
|
||||||
|
<center><a href="/app/about">About</label></center>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>
|
||||||
|
<%= page_title %>
|
||||||
|
</title>
|
||||||
|
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" />
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link
|
||||||
|
href="https://fonts.googleapis.com/css2?family=Ubuntu:ital,wght@0,300;0,400;0,500;0,700;1,300;1,400;1,500;1,700&display=swap"
|
||||||
|
rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="<%= css_path %>">
|
||||||
|
<script src="<%= js_path %>" defer></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<input type="file" id="file_input" accept=".db" style="display: none;" />
|
||||||
|
<div class="control_div" style="display: flex; flex-direction: row; justify-content: space-between;">
|
||||||
|
<label style="font-size: 200%;">CRTelemetry SQLite3</label>
|
||||||
|
<div style="display: flex;align-items: center;">
|
||||||
|
<label id="logout" style="cursor: pointer;"><span>🡄 </span>Logout</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="main_div">
|
||||||
|
<label style="font-size: 150%;">
|
||||||
|
<span>
|
||||||
|
<%- error_emoji %>
|
||||||
|
</span>
|
||||||
|
<%= error_message %>
|
||||||
|
</label>
|
||||||
|
<br>
|
||||||
|
<a href="/app"><span>🢀 </span>Return</a>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>
|
||||||
|
<%= page_title %>
|
||||||
|
</title>
|
||||||
|
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" />
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link
|
||||||
|
href="https://fonts.googleapis.com/css2?family=Ubuntu:ital,wght@0,300;0,400;0,500;0,700;1,300;1,400;1,500;1,700&display=swap"
|
||||||
|
rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="<%= css_path %>">
|
||||||
|
<script src="<%= js_path %>" defer></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<input type="file" id="file_input" accept=".csv" style="display: none;" />
|
||||||
|
<div class="control_div" style="display: flex; flex-direction: row; justify-content: space-between;">
|
||||||
|
<label style="font-size: 200%;">
|
||||||
|
<%= page_title %>
|
||||||
|
<%= program %>
|
||||||
|
</label>
|
||||||
|
<div style="display: flex;align-items: center;">
|
||||||
|
<label id="add" style="cursor: pointer; margin-right: 10px;"><span>+ </span>Add</label>
|
||||||
|
<label id="upload" style="cursor: pointer; margin-left: 10px; margin-right: 10px;"><span>🡅
|
||||||
|
</span>Upload</label>
|
||||||
|
<label id="logout" style="cursor: pointer; margin-left: 10px;"><span>🡄 </span>Logout</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="main_div">
|
||||||
|
<center><label>Main</label></center>
|
||||||
|
<table id="sql_table">
|
||||||
|
<tr>
|
||||||
|
<th>Filename</th>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>id</th>
|
||||||
|
<th>Description</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<%- table_data %>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="about_div">
|
||||||
|
<center><a href="/app/about">About</label></center>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
Generated
+2683
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"better-sqlite3": "^11.10.0",
|
||||||
|
"chart.js": "^4.4.9",
|
||||||
|
"ejs": "^3.1.10",
|
||||||
|
"express": "^5.1.0",
|
||||||
|
"express-basic-auth": "^1.2.1",
|
||||||
|
"express-fileupload": "^1.5.1",
|
||||||
|
"expressjs": "^1.0.1",
|
||||||
|
"fast-csv": "^5.0.2",
|
||||||
|
"socketcan": "^4.0.6",
|
||||||
|
"uuid": "^11.1.0",
|
||||||
|
"winston": "^3.17.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
const express = require("express");
|
||||||
|
const path = require("path");
|
||||||
|
const fileUpload = require("express-fileupload");
|
||||||
|
const basicAuth = require("express-basic-auth");
|
||||||
|
const fs = require("fs");
|
||||||
|
const app_conf = require("./app/config/app.conf.js");
|
||||||
|
|
||||||
|
const public_routes = require('./app/routes/public_routes');
|
||||||
|
const private_routes = require('./app/routes/private_routes');
|
||||||
|
|
||||||
|
const logger = require("./app/utils/logger.util.js");
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
var shutting_down = false;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Static middleware - serve css and js files to public
|
||||||
|
*/
|
||||||
|
app.use(express.json());
|
||||||
|
app.use(fileUpload({
|
||||||
|
limits: { fileSize: 102400 },
|
||||||
|
useTempFiles: true,
|
||||||
|
tempFileDir: path.join(__dirname, app_conf.temp_directory)
|
||||||
|
}));
|
||||||
|
|
||||||
|
app.use("/", public_routes);
|
||||||
|
|
||||||
|
app.use("/app", basicAuth({
|
||||||
|
users: {
|
||||||
|
[app_conf.username]: app_conf.password
|
||||||
|
},
|
||||||
|
challenge: true
|
||||||
|
}), private_routes);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Initialise ExpressJS
|
||||||
|
* Using ejs to pass variables from ExpressJS to HTML pages.
|
||||||
|
*/
|
||||||
|
app.set("view engine", "ejs");
|
||||||
|
app.set("views", path.join(__dirname, app_conf.views_directory));
|
||||||
|
const server = app.listen(app_conf.port, (error) => {
|
||||||
|
logger.info("Starting Database Editor.");
|
||||||
|
|
||||||
|
|
||||||
|
fs.rm(path.join(__dirname, app_conf.temp_directory, "/."), { recursive: true }, (error) => {
|
||||||
|
if (error == null) {
|
||||||
|
logger.info("Removed temp directory.");
|
||||||
|
} else if (error.code === "ENOENT") {
|
||||||
|
logger.info("Temp directory not found.");
|
||||||
|
} else {
|
||||||
|
logger.error(error.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
logger.error(error.message);
|
||||||
|
shutdown();
|
||||||
|
} else {
|
||||||
|
logger.info(`Listening to port ${app_conf.port}.`);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Handle Ctrl+C to shutdown gracefully
|
||||||
|
*/
|
||||||
|
process.on("SIGINT", () => {
|
||||||
|
if (shutting_down) {
|
||||||
|
logger.emerg("Shutting down forcefully.");
|
||||||
|
process.exit(-1);
|
||||||
|
} else {
|
||||||
|
shutdown();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This function shuts the server down
|
||||||
|
*/
|
||||||
|
function shutdown() {
|
||||||
|
shutting_down = true;
|
||||||
|
|
||||||
|
server.close(() => {
|
||||||
|
logger.info("Shutting down server.");
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
body {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background-color: #121212;
|
||||||
|
|
||||||
|
font-family: "Ubuntu", sans-serif;
|
||||||
|
font-weight: 500;
|
||||||
|
font-style: normal;
|
||||||
|
|
||||||
|
-webkit-user-select: none;
|
||||||
|
/* Safari */
|
||||||
|
-ms-user-select: none;
|
||||||
|
/* IE 10 and IE 11 */
|
||||||
|
user-select: none;
|
||||||
|
/* Standard syntax */
|
||||||
|
}
|
||||||
|
|
||||||
|
a:visited {
|
||||||
|
color: blue;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: white;
|
||||||
|
background-color: blue;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control_div {
|
||||||
|
color: #ececec;
|
||||||
|
background-color: #272727;
|
||||||
|
margin: 3px;
|
||||||
|
padding: 5px;
|
||||||
|
border-radius: 5px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main_div {
|
||||||
|
color: #ececec;
|
||||||
|
background-color: #272727;
|
||||||
|
margin: 3px;
|
||||||
|
padding: 5px;
|
||||||
|
border-radius: 5px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.about_div {
|
||||||
|
color: #ececec;
|
||||||
|
background-color: #272727;
|
||||||
|
margin: 3px;
|
||||||
|
padding: 5px;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
background-color: #272727;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 5px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
th,
|
||||||
|
td {
|
||||||
|
border: 2px solid black;
|
||||||
|
padding-top: 5px;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="text"],
|
||||||
|
textarea {
|
||||||
|
background-color: #272727;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
@@ -0,0 +1,380 @@
|
|||||||
|
const commit = document.getElementById("commit");
|
||||||
|
const motor_table = document.getElementById("motor_table");
|
||||||
|
const motor_plot = document.getElementById("motor_plot");
|
||||||
|
const throttle_table = document.getElementById("throttle_table");
|
||||||
|
const throttle_plot = document.getElementById("throttle_plot");
|
||||||
|
const map_name = document.getElementById("map_name");
|
||||||
|
const map_id = document.getElementById("map_id");
|
||||||
|
const map_description = document.getElementById("map_description");
|
||||||
|
|
||||||
|
var changes = 0;
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
let data = [];
|
||||||
|
|
||||||
|
const throttle_percentage_row = document.createElement("tr"),
|
||||||
|
throttle_output_row = document.createElement("tr");
|
||||||
|
|
||||||
|
throttle_percentage_row.appendChild(Object.assign(document.createElement("th"), { textContent: "Throttle Value" }));
|
||||||
|
throttle_output_row.appendChild(Object.assign(document.createElement("th"), { textContent: "Throttle output" }));
|
||||||
|
|
||||||
|
for (let i = 0; i <= steps; i++) {
|
||||||
|
let c_percentage = i * ((max_throttle * 100) / steps);
|
||||||
|
let c_output = 0;
|
||||||
|
|
||||||
|
data.push({ percentage: c_percentage, output: c_output });
|
||||||
|
|
||||||
|
throttle_percentage_row.appendChild(Object.assign(document.createElement("td"), { textContent: `${c_percentage}%` }));
|
||||||
|
|
||||||
|
throttle_output_row.appendChild(
|
||||||
|
Object.assign(document.createElement("td"), {})
|
||||||
|
).appendChild(
|
||||||
|
(() => {
|
||||||
|
const input = Object.assign(document.createElement("input"), {
|
||||||
|
type: "text",
|
||||||
|
style: "width: 100%; height: 100%; box-sizing: border-box; font-size: 16px;",
|
||||||
|
placeholder: c_output
|
||||||
|
})
|
||||||
|
|
||||||
|
input.onchange = async function () {
|
||||||
|
if (this.value) {
|
||||||
|
let value = parseFloat(this.value);
|
||||||
|
if (!isNaN(value) && (value != this.placeholder)) {
|
||||||
|
if (this.style.backgroundColor != "blue") {
|
||||||
|
changes++;
|
||||||
|
}
|
||||||
|
this.parentElement.style.backgroundColor = "blue";
|
||||||
|
this.style.backgroundColor = "blue";
|
||||||
|
this.value = value;
|
||||||
|
|
||||||
|
throttle_chart.data.datasets[0].data[this.parentElement.cellIndex - 1] = value;
|
||||||
|
throttle_chart.update();
|
||||||
|
} else {
|
||||||
|
if (this.style.backgroundColor == "blue") {
|
||||||
|
this.parentElement.style.backgroundColor = "";
|
||||||
|
this.style.backgroundColor = "";
|
||||||
|
changes--;
|
||||||
|
|
||||||
|
throttle_chart.data.datasets[0].data[this.parentElement.cellIndex - 1] = this.placeholder;
|
||||||
|
throttle_chart.update();
|
||||||
|
}
|
||||||
|
this.value = "";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.parentElement.style.backgroundColor = "";
|
||||||
|
this.style.backgroundColor = "";
|
||||||
|
changes--;
|
||||||
|
|
||||||
|
throttle_chart.data.datasets[0].data[this.parentElement.cellIndex - 1] = this.placeholder;
|
||||||
|
throttle_chart.update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return input;
|
||||||
|
})()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
throttle_table.appendChild(throttle_percentage_row);
|
||||||
|
throttle_table.appendChild(throttle_output_row);
|
||||||
|
|
||||||
|
const throttle_chart = new Chart(
|
||||||
|
throttle_plot,
|
||||||
|
{
|
||||||
|
type: "line",
|
||||||
|
data: {
|
||||||
|
labels: data.map(row => row.percentage),
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: "Throttle output",
|
||||||
|
data: data.map(row => row.output)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
})();
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
let data = [];
|
||||||
|
|
||||||
|
const rpm_row = document.createElement("tr"),
|
||||||
|
motor_current_row = document.createElement("tr"),
|
||||||
|
motor_brake_current_row = document.createElement("tr");
|
||||||
|
|
||||||
|
rpm_row.appendChild(Object.assign(document.createElement("th"), { textContent: "RPM" }));
|
||||||
|
motor_current_row.appendChild(Object.assign(document.createElement("th"), { textContent: "Max motor current" }));
|
||||||
|
motor_brake_current_row.appendChild(Object.assign(document.createElement("th"), { textContent: "Max motor brake current" }));
|
||||||
|
|
||||||
|
for (let i = 0; i <= steps; i++) {
|
||||||
|
let c_rpm = i * (max_rpm / steps);
|
||||||
|
let c_mc = 0;
|
||||||
|
let c_mbc = 0;
|
||||||
|
data.push({ rpm: c_rpm, motor_current: c_mc, motor_brake_current: c_mbc });
|
||||||
|
|
||||||
|
rpm_row.appendChild(Object.assign(document.createElement("td"), { textContent: c_rpm }));
|
||||||
|
|
||||||
|
motor_current_row.appendChild(
|
||||||
|
Object.assign(document.createElement("td"), {})
|
||||||
|
).appendChild(
|
||||||
|
(() => {
|
||||||
|
const input = Object.assign(document.createElement("input"), {
|
||||||
|
type: "text",
|
||||||
|
style: "width: 100%; height: 100%; box-sizing: border-box; font-size: 16px;",
|
||||||
|
placeholder: c_mc
|
||||||
|
})
|
||||||
|
|
||||||
|
input.onchange = async function () {
|
||||||
|
if (this.value) {
|
||||||
|
let value = parseInt(this.value);
|
||||||
|
if (!isNaN(value) && (value != this.placeholder)) {
|
||||||
|
if (this.style.backgroundColor != "blue") {
|
||||||
|
changes++;
|
||||||
|
}
|
||||||
|
this.parentElement.style.backgroundColor = "blue";
|
||||||
|
this.style.backgroundColor = "blue";
|
||||||
|
this.value = value;
|
||||||
|
|
||||||
|
motor_chart.data.datasets[0].data[this.parentElement.cellIndex - 1] = value;
|
||||||
|
motor_chart.update();
|
||||||
|
} else {
|
||||||
|
if (this.style.backgroundColor == "blue") {
|
||||||
|
this.parentElement.style.backgroundColor = "";
|
||||||
|
this.style.backgroundColor = "";
|
||||||
|
changes--;
|
||||||
|
}
|
||||||
|
this.value = "";
|
||||||
|
|
||||||
|
motor_chart.data.datasets[0].data[this.parentElement.cellIndex - 1] = this.placeholder;
|
||||||
|
motor_chart.update();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.parentElement.style.backgroundColor = "";
|
||||||
|
this.style.backgroundColor = "";
|
||||||
|
changes--;
|
||||||
|
|
||||||
|
motor_chart.data.datasets[0].data[this.parentElement.cellIndex - 1] = this.placeholder;
|
||||||
|
motor_chart.update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return input;
|
||||||
|
})()
|
||||||
|
);
|
||||||
|
|
||||||
|
motor_brake_current_row.appendChild(
|
||||||
|
Object.assign(document.createElement("td"), {})
|
||||||
|
).appendChild(
|
||||||
|
(() => {
|
||||||
|
const input = Object.assign(document.createElement("input"), {
|
||||||
|
type: "text",
|
||||||
|
style: "width: 100%; height: 100%; box-sizing: border-box; font-size: 16px;",
|
||||||
|
placeholder: c_mbc
|
||||||
|
})
|
||||||
|
|
||||||
|
input.onchange = async function () {
|
||||||
|
if (this.value) {
|
||||||
|
let value = parseInt(this.value);
|
||||||
|
if (!isNaN(value) && (value != this.placeholder)) {
|
||||||
|
if (this.style.backgroundColor != "blue") {
|
||||||
|
changes++;
|
||||||
|
}
|
||||||
|
this.parentElement.style.backgroundColor = "blue";
|
||||||
|
this.style.backgroundColor = "blue";
|
||||||
|
this.value = value;
|
||||||
|
|
||||||
|
motor_chart.data.datasets[1].data[this.parentElement.cellIndex - 1] = value;
|
||||||
|
motor_chart.update();
|
||||||
|
} else {
|
||||||
|
if (this.style.backgroundColor == "blue") {
|
||||||
|
this.parentElement.style.backgroundColor = "";
|
||||||
|
this.style.backgroundColor = "";
|
||||||
|
changes--;
|
||||||
|
}
|
||||||
|
this.value = "";
|
||||||
|
|
||||||
|
motor_chart.data.datasets[1].data[this.parentElement.cellIndex - 1] = this.placeholder;
|
||||||
|
motor_chart.update();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.parentElement.style.backgroundColor = "";
|
||||||
|
this.style.backgroundColor = "";
|
||||||
|
changes--;
|
||||||
|
|
||||||
|
motor_chart.data.datasets[1].data[this.parentElement.cellIndex - 1] = this.placeholder;
|
||||||
|
motor_chart.update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return input;
|
||||||
|
})()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
motor_table.appendChild(rpm_row);
|
||||||
|
motor_table.appendChild(motor_current_row);
|
||||||
|
motor_table.appendChild(motor_brake_current_row);
|
||||||
|
|
||||||
|
const motor_chart = new Chart(
|
||||||
|
motor_plot,
|
||||||
|
{
|
||||||
|
type: "line",
|
||||||
|
data: {
|
||||||
|
labels: data.map(row => row.rpm),
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: "Max motor current",
|
||||||
|
data: data.map(row => row.motor_current)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Max motor brake current",
|
||||||
|
data: data.map(row => row.motor_brake_current)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
})();
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
map_name.onchange = async function () {
|
||||||
|
if (this.value == this.placeholder) {
|
||||||
|
this.value = "";
|
||||||
|
this.style.backgroundColor = "";
|
||||||
|
this.parentElement.style.backgroundColor = "";
|
||||||
|
changes--;
|
||||||
|
} else {
|
||||||
|
if (this.style.backgroundColor == "") {
|
||||||
|
this.style.backgroundColor = "blue";
|
||||||
|
this.parentElement.style.backgroundColor = "blue";
|
||||||
|
changes++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
map_id.onchange = async function () {
|
||||||
|
let value = parseInt(this.value);
|
||||||
|
|
||||||
|
if (!isNaN(value)) {
|
||||||
|
if (this.value == this.placeholder) {
|
||||||
|
if (this.style.backgroundColor == "blue") {
|
||||||
|
this.style.backgroundColor = "";
|
||||||
|
this.parentElement.style.backgroundColor = "";
|
||||||
|
changes--;
|
||||||
|
}
|
||||||
|
this.value = "";
|
||||||
|
} else {
|
||||||
|
if (this.style.backgroundColor == "") {
|
||||||
|
this.style.backgroundColor = "blue";
|
||||||
|
this.parentElement.style.backgroundColor = "blue";
|
||||||
|
changes++;
|
||||||
|
}
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (this.style.backgroundColor == "blue") {
|
||||||
|
this.style.backgroundColor = "";
|
||||||
|
this.parentElement.style.backgroundColor = "";
|
||||||
|
changes--;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.value = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
map_description.onchange = async function () {
|
||||||
|
if (this.value == this.placeholder) {
|
||||||
|
this.value = "";
|
||||||
|
this.style.backgroundColor = "";
|
||||||
|
this.parentElement.style.backgroundColor = "";
|
||||||
|
changes--;
|
||||||
|
} else {
|
||||||
|
if (this.style.backgroundColor == "") {
|
||||||
|
this.style.backgroundColor = "blue";
|
||||||
|
this.parentElement.style.backgroundColor = "blue";
|
||||||
|
changes++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function setEventListeners(element, cb) {
|
||||||
|
element.onmouseenter = async function () {
|
||||||
|
this.style.backgroundColor = "blue";
|
||||||
|
};
|
||||||
|
element.onmouseleave = async function () {
|
||||||
|
this.style.backgroundColor = "";
|
||||||
|
};
|
||||||
|
element.onmousedown = async function () {
|
||||||
|
this.style.backgroundColor = "red";
|
||||||
|
};
|
||||||
|
element.ondblclick = async function () {
|
||||||
|
cb();
|
||||||
|
this.style.backgroundColor = "blue";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
setEventListeners(commit, async () => {
|
||||||
|
if (changes) {
|
||||||
|
let req_body = {};
|
||||||
|
|
||||||
|
req_body["meta"] = {};
|
||||||
|
req_body["throttle"] = {};
|
||||||
|
req_body["motor"] = { 0: {}, 1: {} };
|
||||||
|
|
||||||
|
if (map_name.style.backgroundColor == "blue") {
|
||||||
|
req_body["meta"]["name"] = map_name.value;
|
||||||
|
}
|
||||||
|
if (map_id.style.backgroundColor == "blue") {
|
||||||
|
req_body["meta"]["id"] = map_id.value;
|
||||||
|
}
|
||||||
|
if (map_description.style.backgroundColor == "blue") {
|
||||||
|
req_body["meta"]["description"] = map_description.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 1; i < throttle_table.rows[1].cells.length; i++) {
|
||||||
|
let e = throttle_table.rows[1].cells[i].firstChild;
|
||||||
|
req_body["throttle"][i - 1] = (e.value ? e.value : e.placeholder);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 1; i < motor_table.rows[1].cells.length; i++) {
|
||||||
|
let e = motor_table.rows[1].cells[i].firstChild;
|
||||||
|
req_body["motor"][0][i - 1] = (e.value ? e.value : e.placeholder);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 1; i < motor_table.rows[2].cells.length; i++) {
|
||||||
|
let e = motor_table.rows[2].cells[i].firstChild;
|
||||||
|
req_body["motor"][1][i - 1] = (e.value ? e.value : e.placeholder);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch("/app/add", {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify(req_body),
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Response status: ${response.status}`);
|
||||||
|
} else {
|
||||||
|
window.location.href = "/app";
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
alert(`Add unsuccessful. ${error.message}`);
|
||||||
|
console.error(error.message);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
alert("Nothing to commit.");
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -0,0 +1,426 @@
|
|||||||
|
const commit = document.getElementById("commit");
|
||||||
|
const download = document.getElementById("download");
|
||||||
|
const remove = document.getElementById("remove");
|
||||||
|
const motor_table = document.getElementById("motor_table");
|
||||||
|
const motor_plot = document.getElementById("motor_plot");
|
||||||
|
const throttle_table = document.getElementById("throttle_table");
|
||||||
|
const throttle_plot = document.getElementById("throttle_plot");
|
||||||
|
const map_name = document.getElementById("map_name");
|
||||||
|
const map_id = document.getElementById("map_id");
|
||||||
|
const map_description = document.getElementById("map_description");
|
||||||
|
|
||||||
|
var changes = 0;
|
||||||
|
|
||||||
|
var parsed_map = JSON.parse(map);
|
||||||
|
(function () {
|
||||||
|
let data = [];
|
||||||
|
|
||||||
|
const throttle_percentage_row = document.createElement("tr"),
|
||||||
|
throttle_output_row = document.createElement("tr");
|
||||||
|
|
||||||
|
throttle_percentage_row.appendChild(Object.assign(document.createElement("th"), { textContent: "Throttle Value" }));
|
||||||
|
throttle_output_row.appendChild(Object.assign(document.createElement("th"), { textContent: "Throttle output" }));
|
||||||
|
|
||||||
|
for (const i in Object.keys(parsed_map)) {
|
||||||
|
let c_percentage = i * ((max_throttle * 100) / steps);
|
||||||
|
let c_output = parseFloat(parsed_map[i][2]);
|
||||||
|
|
||||||
|
data.push({ percentage: c_percentage, output: c_output });
|
||||||
|
|
||||||
|
throttle_percentage_row.appendChild(Object.assign(document.createElement("td"), { textContent: `${c_percentage}%` }));
|
||||||
|
|
||||||
|
throttle_output_row.appendChild(
|
||||||
|
Object.assign(document.createElement("td"), {})
|
||||||
|
).appendChild(
|
||||||
|
(() => {
|
||||||
|
const input = Object.assign(document.createElement("input"), {
|
||||||
|
type: "text",
|
||||||
|
style: "width: 100%; height: 100%; box-sizing: border-box; font-size: 16px;",
|
||||||
|
placeholder: c_output
|
||||||
|
})
|
||||||
|
|
||||||
|
input.onchange = async function () {
|
||||||
|
if (this.value) {
|
||||||
|
let value = parseFloat(this.value);
|
||||||
|
if (!isNaN(value) && (value != this.placeholder)) {
|
||||||
|
if (this.style.backgroundColor != "blue") {
|
||||||
|
changes++;
|
||||||
|
}
|
||||||
|
this.parentElement.style.backgroundColor = "blue";
|
||||||
|
this.style.backgroundColor = "blue";
|
||||||
|
this.value = value;
|
||||||
|
|
||||||
|
throttle_chart.data.datasets[0].data[this.parentElement.cellIndex - 1] = value;
|
||||||
|
throttle_chart.update();
|
||||||
|
} else {
|
||||||
|
if (this.style.backgroundColor == "blue") {
|
||||||
|
this.parentElement.style.backgroundColor = "";
|
||||||
|
this.style.backgroundColor = "";
|
||||||
|
changes--;
|
||||||
|
|
||||||
|
throttle_chart.data.datasets[0].data[this.parentElement.cellIndex - 1] = this.placeholder;
|
||||||
|
throttle_chart.update();
|
||||||
|
}
|
||||||
|
this.value = "";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.parentElement.style.backgroundColor = "";
|
||||||
|
this.style.backgroundColor = "";
|
||||||
|
changes--;
|
||||||
|
|
||||||
|
throttle_chart.data.datasets[0].data[this.parentElement.cellIndex - 1] = this.placeholder;
|
||||||
|
throttle_chart.update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return input;
|
||||||
|
})()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
throttle_table.appendChild(throttle_percentage_row);
|
||||||
|
throttle_table.appendChild(throttle_output_row);
|
||||||
|
|
||||||
|
const throttle_chart = new Chart(
|
||||||
|
throttle_plot,
|
||||||
|
{
|
||||||
|
type: "line",
|
||||||
|
data: {
|
||||||
|
labels: data.map(row => row.percentage),
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: "Throttle output",
|
||||||
|
data: data.map(row => row.output)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
})();
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
let data = [];
|
||||||
|
|
||||||
|
const rpm_row = document.createElement("tr"),
|
||||||
|
motor_current_row = document.createElement("tr"),
|
||||||
|
motor_brake_current_row = document.createElement("tr");
|
||||||
|
|
||||||
|
rpm_row.appendChild(Object.assign(document.createElement("th"), { textContent: "RPM" }));
|
||||||
|
motor_current_row.appendChild(Object.assign(document.createElement("th"), { textContent: "Max motor current" }));
|
||||||
|
motor_brake_current_row.appendChild(Object.assign(document.createElement("th"), { textContent: "Max motor brake current" }));
|
||||||
|
|
||||||
|
for (const i in Object.keys(parsed_map)) {
|
||||||
|
let c_rpm = i * (max_rpm / steps);
|
||||||
|
let c_mc = parseInt(parsed_map[i][0]);
|
||||||
|
let c_mbc = parseInt(parsed_map[i][1]);
|
||||||
|
data.push({ rpm: c_rpm, motor_current: c_mc, motor_brake_current: c_mbc });
|
||||||
|
|
||||||
|
rpm_row.appendChild(Object.assign(document.createElement("td"), { textContent: c_rpm }));
|
||||||
|
|
||||||
|
motor_current_row.appendChild(
|
||||||
|
Object.assign(document.createElement("td"), {})
|
||||||
|
).appendChild(
|
||||||
|
(() => {
|
||||||
|
const input = Object.assign(document.createElement("input"), {
|
||||||
|
type: "text",
|
||||||
|
style: "width: 100%; height: 100%; box-sizing: border-box; font-size: 16px;",
|
||||||
|
placeholder: c_mc
|
||||||
|
})
|
||||||
|
|
||||||
|
input.onchange = async function () {
|
||||||
|
if (this.value) {
|
||||||
|
let value = parseInt(this.value);
|
||||||
|
if (!isNaN(value) && (value != this.placeholder)) {
|
||||||
|
if (this.style.backgroundColor != "blue") {
|
||||||
|
changes++;
|
||||||
|
}
|
||||||
|
this.parentElement.style.backgroundColor = "blue";
|
||||||
|
this.style.backgroundColor = "blue";
|
||||||
|
this.value = value;
|
||||||
|
|
||||||
|
motor_chart.data.datasets[0].data[this.parentElement.cellIndex - 1] = value;
|
||||||
|
motor_chart.update();
|
||||||
|
} else {
|
||||||
|
if (this.style.backgroundColor == "blue") {
|
||||||
|
this.parentElement.style.backgroundColor = "";
|
||||||
|
this.style.backgroundColor = "";
|
||||||
|
changes--;
|
||||||
|
}
|
||||||
|
this.value = "";
|
||||||
|
|
||||||
|
motor_chart.data.datasets[0].data[this.parentElement.cellIndex - 1] = this.placeholder;
|
||||||
|
motor_chart.update();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.parentElement.style.backgroundColor = "";
|
||||||
|
this.style.backgroundColor = "";
|
||||||
|
changes--;
|
||||||
|
|
||||||
|
motor_chart.data.datasets[0].data[this.parentElement.cellIndex - 1] = this.placeholder;
|
||||||
|
motor_chart.update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return input;
|
||||||
|
})()
|
||||||
|
);
|
||||||
|
|
||||||
|
motor_brake_current_row.appendChild(
|
||||||
|
Object.assign(document.createElement("td"), {})
|
||||||
|
).appendChild(
|
||||||
|
(() => {
|
||||||
|
const input = Object.assign(document.createElement("input"), {
|
||||||
|
type: "text",
|
||||||
|
style: "width: 100%; height: 100%; box-sizing: border-box; font-size: 16px;",
|
||||||
|
placeholder: c_mbc
|
||||||
|
})
|
||||||
|
|
||||||
|
input.onchange = async function () {
|
||||||
|
if (this.value) {
|
||||||
|
let value = parseInt(this.value);
|
||||||
|
if (!isNaN(value) && (value != this.placeholder)) {
|
||||||
|
if (this.style.backgroundColor != "blue") {
|
||||||
|
changes++;
|
||||||
|
}
|
||||||
|
this.parentElement.style.backgroundColor = "blue";
|
||||||
|
this.style.backgroundColor = "blue";
|
||||||
|
this.value = value;
|
||||||
|
|
||||||
|
motor_chart.data.datasets[1].data[this.parentElement.cellIndex - 1] = value;
|
||||||
|
motor_chart.update();
|
||||||
|
} else {
|
||||||
|
if (this.style.backgroundColor == "blue") {
|
||||||
|
this.parentElement.style.backgroundColor = "";
|
||||||
|
this.style.backgroundColor = "";
|
||||||
|
changes--;
|
||||||
|
}
|
||||||
|
this.value = "";
|
||||||
|
|
||||||
|
motor_chart.data.datasets[1].data[this.parentElement.cellIndex - 1] = this.placeholder;
|
||||||
|
motor_chart.update();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.parentElement.style.backgroundColor = "";
|
||||||
|
this.style.backgroundColor = "";
|
||||||
|
changes--;
|
||||||
|
|
||||||
|
motor_chart.data.datasets[1].data[this.parentElement.cellIndex - 1] = this.placeholder;
|
||||||
|
motor_chart.update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return input;
|
||||||
|
})()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
motor_table.appendChild(rpm_row);
|
||||||
|
motor_table.appendChild(motor_current_row);
|
||||||
|
motor_table.appendChild(motor_brake_current_row);
|
||||||
|
|
||||||
|
const motor_chart = new Chart(
|
||||||
|
motor_plot,
|
||||||
|
{
|
||||||
|
type: "line",
|
||||||
|
data: {
|
||||||
|
labels: data.map(row => row.rpm),
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: "Max motor current",
|
||||||
|
data: data.map(row => row.motor_current)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Max motor brake current",
|
||||||
|
data: data.map(row => row.motor_brake_current)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
})();
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
const map_meta_json = JSON.parse(map_meta);
|
||||||
|
|
||||||
|
if ("name" in map_meta_json) {
|
||||||
|
map_name.placeholder = map_meta_json["name"];
|
||||||
|
map_name.value = "";
|
||||||
|
}
|
||||||
|
if ("id" in map_meta_json) {
|
||||||
|
map_id.placeholder = map_meta_json["id"];
|
||||||
|
map_id.value = "";
|
||||||
|
}
|
||||||
|
if ("description" in map_meta_json) {
|
||||||
|
map_description.placeholder = map_meta_json["description"];
|
||||||
|
map_description.value = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
map_name.onchange = async function () {
|
||||||
|
if (this.value == this.placeholder) {
|
||||||
|
this.value = "";
|
||||||
|
this.style.backgroundColor = "";
|
||||||
|
this.parentElement.style.backgroundColor = "";
|
||||||
|
changes--;
|
||||||
|
} else {
|
||||||
|
if (this.style.backgroundColor == "") {
|
||||||
|
this.style.backgroundColor = "blue";
|
||||||
|
this.parentElement.style.backgroundColor = "blue";
|
||||||
|
changes++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
map_id.onchange = async function () {
|
||||||
|
let value = parseInt(this.value);
|
||||||
|
|
||||||
|
if (!isNaN(value)) {
|
||||||
|
if (this.value == this.placeholder) {
|
||||||
|
if (this.style.backgroundColor == "blue") {
|
||||||
|
this.style.backgroundColor = "";
|
||||||
|
this.parentElement.style.backgroundColor = "";
|
||||||
|
changes--;
|
||||||
|
}
|
||||||
|
this.value = "";
|
||||||
|
} else {
|
||||||
|
if (this.style.backgroundColor == "") {
|
||||||
|
this.style.backgroundColor = "blue";
|
||||||
|
this.parentElement.style.backgroundColor = "blue";
|
||||||
|
changes++;
|
||||||
|
}
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (this.style.backgroundColor == "blue") {
|
||||||
|
this.style.backgroundColor = "";
|
||||||
|
this.parentElement.style.backgroundColor = "";
|
||||||
|
changes--;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.value = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
map_description.onchange = async function () {
|
||||||
|
if (this.value == this.placeholder) {
|
||||||
|
this.value = "";
|
||||||
|
this.style.backgroundColor = "";
|
||||||
|
this.parentElement.style.backgroundColor = "";
|
||||||
|
changes--;
|
||||||
|
} else {
|
||||||
|
if (this.style.backgroundColor == "") {
|
||||||
|
this.style.backgroundColor = "blue";
|
||||||
|
this.parentElement.style.backgroundColor = "blue";
|
||||||
|
changes++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function setEventListeners(element, cb) {
|
||||||
|
element.onmouseenter = async function () {
|
||||||
|
this.style.backgroundColor = "blue";
|
||||||
|
};
|
||||||
|
element.onmouseleave = async function () {
|
||||||
|
this.style.backgroundColor = "";
|
||||||
|
};
|
||||||
|
element.onmousedown = async function () {
|
||||||
|
this.style.backgroundColor = "red";
|
||||||
|
};
|
||||||
|
element.ondblclick = async function () {
|
||||||
|
cb();
|
||||||
|
this.style.backgroundColor = "blue";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
setEventListeners(remove, async () => {
|
||||||
|
const map_filename = (new URLSearchParams(window.location.search)).get("element");
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/app/remove?element=${map_filename}`, {
|
||||||
|
method: "DELETE"
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Response status: ${response.status}`);
|
||||||
|
} else {
|
||||||
|
window.location.href = "/app";
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
alert(`Remove unsuccessful. ${error.message}`);
|
||||||
|
console.error(error.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
setEventListeners(commit, async () => {
|
||||||
|
if (changes) {
|
||||||
|
const map_filename = (new URLSearchParams(window.location.search)).get("element");
|
||||||
|
let req_body = {};
|
||||||
|
|
||||||
|
req_body["meta"] = {};
|
||||||
|
req_body["throttle"] = {};
|
||||||
|
req_body["motor"] = { 0: {}, 1: {} };
|
||||||
|
|
||||||
|
if (map_name.style.backgroundColor == "blue") {
|
||||||
|
req_body["meta"]["name"] = map_name.value;
|
||||||
|
}
|
||||||
|
if (map_id.style.backgroundColor == "blue") {
|
||||||
|
req_body["meta"]["id"] = map_id.value;
|
||||||
|
}
|
||||||
|
if (map_description.style.backgroundColor == "blue") {
|
||||||
|
req_body["meta"]["description"] = map_description.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 1; i < throttle_table.rows[1].cells.length; i++) {
|
||||||
|
if (throttle_table.rows[1].cells[i].style.backgroundColor == "blue") {
|
||||||
|
req_body["throttle"][i - 1] = throttle_table.rows[1].cells[i].firstChild.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 1; i < motor_table.rows[1].cells.length; i++) {
|
||||||
|
if (motor_table.rows[1].cells[i].style.backgroundColor == "blue") {
|
||||||
|
req_body["motor"][0][i - 1] = motor_table.rows[1].cells[i].firstChild.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 1; i < motor_table.rows[2].cells.length; i++) {
|
||||||
|
if (motor_table.rows[2].cells[i].style.backgroundColor == "blue") {
|
||||||
|
req_body["motor"][1][i - 1] = motor_table.rows[2].cells[i].firstChild.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/app/edit?element=${map_filename}`, {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify(req_body),
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Response status: ${response.status}`);
|
||||||
|
} else {
|
||||||
|
window.location.href = "/app";
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
alert(`Edit unsuccessful. ${error.message}`);
|
||||||
|
console.error(error.message);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
alert("Nothing to commit.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
setEventListeners(download, () => {
|
||||||
|
const map_filename = (new URLSearchParams(window.location.search)).get("element");
|
||||||
|
|
||||||
|
window.location.href = `/app/download?element=${map_filename}`;
|
||||||
|
});
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
const logout = document.getElementById("logout");
|
||||||
|
|
||||||
|
logout.onmouseenter = async function () {
|
||||||
|
this.style.backgroundColor = "blue";
|
||||||
|
};
|
||||||
|
logout.onmouseleave = async function () {
|
||||||
|
this.style.backgroundColor = "";
|
||||||
|
};
|
||||||
|
logout.onmousedown = async function () {
|
||||||
|
this.style.backgroundColor = "red";
|
||||||
|
};
|
||||||
|
logout.ondblclick = async function () {
|
||||||
|
this.style.backgroundColor = "blue";
|
||||||
|
window.location.href = `${window.location.protocol}//logout@${window.location.host}/`;
|
||||||
|
};
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
const sql_table = document.getElementById("sql_table");
|
||||||
|
const upload = document.getElementById("upload");
|
||||||
|
const logout = document.getElementById("logout");
|
||||||
|
const add = document.getElementById("add");
|
||||||
|
|
||||||
|
const sql_rows = sql_table.rows;
|
||||||
|
|
||||||
|
function setEventListeners(element, cb) {
|
||||||
|
element.onmouseenter = async function () {
|
||||||
|
this.style.backgroundColor = "blue";
|
||||||
|
};
|
||||||
|
element.onmouseleave = async function () {
|
||||||
|
this.style.backgroundColor = "";
|
||||||
|
};
|
||||||
|
element.onmousedown = async function () {
|
||||||
|
this.style.backgroundColor = "red";
|
||||||
|
};
|
||||||
|
element.ondblclick = async function () {
|
||||||
|
this.style.backgroundColor = "blue";
|
||||||
|
cb();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
setEventListeners(upload, async () => {
|
||||||
|
const input = document.getElementById("file_input");
|
||||||
|
const upload = (file) => {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('file', file);
|
||||||
|
|
||||||
|
fetch('/app/upload', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
}).then((response) => {
|
||||||
|
if (!response.ok) {
|
||||||
|
alert(`Upload unsuccessful. ${response.status}`);
|
||||||
|
} else {
|
||||||
|
location.reload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const onSelectFile = () => upload(input.files[0]);
|
||||||
|
input.addEventListener('change', onSelectFile, false);
|
||||||
|
input.click();
|
||||||
|
});
|
||||||
|
|
||||||
|
setEventListeners(add, () => {
|
||||||
|
window.location.href = "/app/add";
|
||||||
|
});
|
||||||
|
|
||||||
|
setEventListeners(logout, () => {
|
||||||
|
window.location.href = `${window.location.protocol}//logout@${window.location.host}/`;
|
||||||
|
});
|
||||||
|
|
||||||
|
for (let i = 1; i < sql_rows.length; i++) {
|
||||||
|
setEventListeners(sql_rows[i], () => {
|
||||||
|
window.location.href = `/app/edit?element=${sql_rows[i].cells[0].innerText}`;
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user