Initial commit

This commit is contained in:
2025-05-29 12:33:41 +03:00
commit 5d994126cb
19 changed files with 4618 additions and 0 deletions
+149
View File
@@ -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
+204
View File
@@ -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, "&#10067;", messages.empty_db);
}
} catch (error) {
errorPageRenderer(res, "&#127755;&#128165;", 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;
+26
View File
@@ -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;
+24
View File
@@ -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;
+180
View File
@@ -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
};
+34
View File
@@ -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>&#129152; </span>Return</a>
</div>
</body>
</html>
+73
View File
@@ -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>&#129095;
</span>Download</label>
<label id="remove" style="cursor: pointer; margin-left: 10px; margin-right: 10px;"><span>&#45;
</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>&#129152; </span>Return</a>
<label id="commit" style="cursor: pointer;"><span>&#9989; </span>Commit</label>
</div>
</div>
<div class="about_div">
<center><a href="/app/about">About</label></center>
</div>
</body>
</html>
+33
View File
@@ -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>
+70
View File
@@ -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>&#129095;
</span>Download</label>
<label id="remove" style="cursor: pointer; margin-left: 10px; margin-right: 10px;"><span>&#45;
</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>&#129152; </span>Return</a>
<label id="commit" style="cursor: pointer;"><span>&#9989; </span>Commit</label>
</div>
</div>
<div class="about_div">
<center><a href="/app/about">About</label></center>
</div>
</body>
</html>
+39
View File
@@ -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>&#129092; </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>&#129152; </span>Return</a>
</div>
</body>
</html>
+52
View File
@@ -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>&#43; </span>Add</label>
<label id="upload" style="cursor: pointer; margin-left: 10px; margin-right: 10px;"><span>&#129093;
</span>Upload</label>
<label id="logout" style="cursor: pointer; margin-left: 10px;"><span>&#129092; </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>
+2683
View File
File diff suppressed because it is too large Load Diff
+15
View File
@@ -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"
}
}
+84
View File
@@ -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.");
});
}
+73
View File
@@ -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;
}
+380
View File
@@ -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.");
}
});
+426
View File
@@ -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}`;
});
+15
View File
@@ -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}/`;
};
+58
View File
@@ -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}`;
});
}