Initial commit
This commit is contained in:
+140
@@ -0,0 +1,140 @@
|
|||||||
|
# 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/
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
module.exports = {
|
||||||
|
name: "CRTelemetry",
|
||||||
|
port: 80,
|
||||||
|
logger: {
|
||||||
|
level: "info",
|
||||||
|
error_path: "/app/logs/error.log",
|
||||||
|
full_path: "/app/logs/combined.log"
|
||||||
|
},
|
||||||
|
timestamp_format: "HH:mm:ss DD-MM-YYYY",
|
||||||
|
views_directory: "/app/views/",
|
||||||
|
temp_directory: "/app/temp/",
|
||||||
|
web_paths: {
|
||||||
|
css: "/app/src/css",
|
||||||
|
js: "/app/src/javascript"
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
module.exports = {
|
||||||
|
path: "./sensors.db",
|
||||||
|
table_name: "sensors"
|
||||||
|
};
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main_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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
const sql_table = document.getElementById("sql_table");
|
||||||
|
const sql_row = sql_table.rows;
|
||||||
|
const commit = document.getElementById("commit");
|
||||||
|
|
||||||
|
column_options = JSON.parse(column_options);
|
||||||
|
console.log(column_options);
|
||||||
|
|
||||||
|
commit.onmouseenter = async function () {
|
||||||
|
this.style.backgroundColor = "blue";
|
||||||
|
}
|
||||||
|
commit.onmouseleave = async function () {
|
||||||
|
this.style.backgroundColor = "";
|
||||||
|
};
|
||||||
|
commit.onmousedown = async function () {
|
||||||
|
this.style.backgroundColor = "red";
|
||||||
|
};
|
||||||
|
commit.ondblclick = async function () {
|
||||||
|
if (sql_row[1].cells[0].firstChild.value) {
|
||||||
|
let req_body = {};
|
||||||
|
|
||||||
|
for (let i = 1; i < sql_row[1].cells.length; i++) {
|
||||||
|
req_body[sql_row[0].cells[i].firstChild.textContent] = sql_row[1].cells[i].firstChild[sql_row[1].cells[i].firstChild.selectedIndex].text;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/app/add?element=${sql_row[1].cells[0].firstChild.value}`, {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify(req_body),
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Response status: ${response.status}`);
|
||||||
|
} else {
|
||||||
|
this.style.backgroundColor = "blue";
|
||||||
|
window.location.href = "/app";
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (let i = 1; i < sql_row[1].cells.length; i++) {
|
||||||
|
let opts = column_options[sql_row[0].cells[i].textContent];
|
||||||
|
|
||||||
|
if (opts != null) {
|
||||||
|
for (o of opts) {
|
||||||
|
sql_row[1].cells[i].firstChild.add(new Option(o, o));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sql_row[1].cells[i].firstChild.oncontextmenu = async function (ev) {
|
||||||
|
ev.preventDefault();
|
||||||
|
let new_value = prompt("Enter a new value");
|
||||||
|
if ((new_value != null) && (new_value != "")) {
|
||||||
|
if (this[this.length - 1].value != "custom") {
|
||||||
|
this.add(new Option(new_value, "custom"));
|
||||||
|
} else {
|
||||||
|
this[this.length - 1].text = new_value;
|
||||||
|
}
|
||||||
|
this.value = "custom";
|
||||||
|
this.dispatchEvent(new Event("change"));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
const sql_table = document.getElementById("sql_table");
|
||||||
|
const sql_row = sql_table.rows;
|
||||||
|
const commit = document.getElementById("commit");
|
||||||
|
const remove = document.getElementById("remove");
|
||||||
|
var changes = 0;
|
||||||
|
|
||||||
|
column_options = JSON.parse(column_options);
|
||||||
|
|
||||||
|
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 () => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/app/remove?element=${sql_row[1].cells[0].firstChild.textContent}`, {
|
||||||
|
method: "DELETE"
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Response status: ${response.status}`);
|
||||||
|
} else {
|
||||||
|
window.location.href = "/app";
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
setEventListeners(commit, async () => {
|
||||||
|
if (changes) {
|
||||||
|
let req_body = {};
|
||||||
|
|
||||||
|
for (let i = 1; i < sql_row[1].cells.length; i++) {
|
||||||
|
if (sql_row[1].cells[i].firstChild.selectedIndex != 0) {
|
||||||
|
req_body[sql_row[0].cells[i].firstChild.textContent] = sql_row[1].cells[i].firstChild[sql_row[1].cells[i].firstChild.selectedIndex].text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/app/edit?element=${sql_row[1].cells[0].firstChild.textContent}`, {
|
||||||
|
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) {
|
||||||
|
console.error(error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
for (let i = 1; i < sql_row[1].cells.length; i++) {
|
||||||
|
let opts = column_options[sql_row[0].cells[i].textContent];
|
||||||
|
|
||||||
|
if (opts != null) {
|
||||||
|
for (o of opts) {
|
||||||
|
sql_row[1].cells[i].firstChild.add(new Option(o, o));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sql_row[1].cells[i].firstChild.oncontextmenu = async function (ev) {
|
||||||
|
ev.preventDefault();
|
||||||
|
let new_value = prompt("Enter a new value");
|
||||||
|
if ((new_value != null) && (new_value != "")) {
|
||||||
|
if (this[this.length - 1].value != "custom") {
|
||||||
|
this.add(new Option(new_value, "custom"));
|
||||||
|
} else {
|
||||||
|
this[this.length - 1].text = new_value;
|
||||||
|
}
|
||||||
|
this.value = "custom";
|
||||||
|
this.dispatchEvent(new Event("change"));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
sql_row[1].cells[i].firstChild.onchange = async function () {
|
||||||
|
if (this.selectedIndex) {
|
||||||
|
console.log(this);
|
||||||
|
this.parentElement.style.backgroundColor = "blue";
|
||||||
|
changes++;
|
||||||
|
} else {
|
||||||
|
this.parentElement.style.backgroundColor = "";
|
||||||
|
changes--;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
const sql_table = document.getElementById("sql_table");
|
||||||
|
const upload = document.getElementById("upload");
|
||||||
|
const download = document.getElementById("download");
|
||||||
|
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) => {
|
||||||
|
location.reload();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const onSelectFile = () => upload(input.files[0]);
|
||||||
|
input.addEventListener('change', onSelectFile, false);
|
||||||
|
input.click();
|
||||||
|
});
|
||||||
|
|
||||||
|
setEventListeners(add, () => {
|
||||||
|
window.location.href = "/app/add";
|
||||||
|
});
|
||||||
|
|
||||||
|
setEventListeners(download, () => {
|
||||||
|
window.location.href = "/app/download";
|
||||||
|
});
|
||||||
|
|
||||||
|
setEventListeners(logout, () => {
|
||||||
|
// fetch logout
|
||||||
|
// check response type
|
||||||
|
// redirect to auth
|
||||||
|
});
|
||||||
|
|
||||||
|
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}`;
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>
|
||||||
|
<%= page_title %>
|
||||||
|
</title>
|
||||||
|
|
||||||
|
<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 defer>
|
||||||
|
var column_options = '<%- column_options %>';
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="control_div">
|
||||||
|
<label style="font-size: 200%;">CRTelemetry SQLite3</label>
|
||||||
|
</div>
|
||||||
|
<div class="main_div">
|
||||||
|
<table id="sql_table" style="text-align: center;">
|
||||||
|
<tr>
|
||||||
|
<%- table_header %>
|
||||||
|
</tr>
|
||||||
|
<%- table_data %>
|
||||||
|
</table>
|
||||||
|
<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>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>
|
||||||
|
<%= page_title %>
|
||||||
|
</title>
|
||||||
|
|
||||||
|
<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 defer>
|
||||||
|
var column_options = '<%- column_options %>';
|
||||||
|
</script>
|
||||||
|
</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 style="display: flex;align-items: center;">
|
||||||
|
<label id="remove" style="cursor: pointer; margin-right: 10px;"><span>- </span>Remove</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="main_div">
|
||||||
|
<table id="sql_table" style="text-align: center;">
|
||||||
|
<tr>
|
||||||
|
<%- table_header %>
|
||||||
|
</tr>
|
||||||
|
<%- table_data %>
|
||||||
|
</table>
|
||||||
|
<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>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>
|
||||||
|
<%= page_title %>
|
||||||
|
</title>
|
||||||
|
|
||||||
|
<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%;'>CRTelemetry
|
||||||
|
SQLite3</label>
|
||||||
|
</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,39 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>
|
||||||
|
<%= page_title %>
|
||||||
|
</title>
|
||||||
|
|
||||||
|
<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="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="download" style="cursor: pointer; margin-left: 10px; margin-right: 10px;"><span>🡇 </span>Download</label>
|
||||||
|
<label id="logout" style="cursor: pointer; margin-left: 10px;"><span>🡄 </span>Logout</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="main_div">
|
||||||
|
<table id="sql_table">
|
||||||
|
<tr>
|
||||||
|
<%- table_header %>
|
||||||
|
</tr>
|
||||||
|
<%- table_data %>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
Generated
+1644
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"better-sqlite3": "^11.10.0",
|
||||||
|
"ejs": "^3.1.10",
|
||||||
|
"express": "^5.1.0",
|
||||||
|
"express-fileupload": "^1.5.1",
|
||||||
|
"winston": "^3.17.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,393 @@
|
|||||||
|
const express = require("express");
|
||||||
|
const Database = require("better-sqlite3");
|
||||||
|
const winston = require("winston");
|
||||||
|
const path = require("path");
|
||||||
|
const fileUpload = require("express-fileupload");
|
||||||
|
const fs = require("fs");
|
||||||
|
const app_conf = require("./app/config/app.conf.js");
|
||||||
|
const db_conf = require("./app/config/db.conf.js");
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
var db;
|
||||||
|
var shutting_down = false;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Static middleware - serve css and js files to public
|
||||||
|
*/
|
||||||
|
app.use(app_conf.web_paths.css, express.static(path.join(__dirname, app_conf.web_paths.css)));
|
||||||
|
app.use(app_conf.web_paths.js, express.static(path.join(__dirname, app_conf.web_paths.js)));
|
||||||
|
app.use(express.json());
|
||||||
|
app.use(fileUpload({
|
||||||
|
limits: { fileSize: 102400 },
|
||||||
|
|
||||||
|
useTempFiles: true,
|
||||||
|
tempFileDir: path.join(__dirname, app_conf.temp_directory)
|
||||||
|
}));
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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(__dirname, app_conf.logger.error_path), level: "error", }),
|
||||||
|
new winston.transports.File({ filename: path.join(__dirname, app_conf.logger.full_path) }),
|
||||||
|
new winston.transports.Console()
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
app.delete("/app/remove", async (req, res) => {
|
||||||
|
if (req.query["element"]) {
|
||||||
|
try {
|
||||||
|
const remove = db.prepare(`DELETE FROM ${db_conf.table_name} WHERE Name='${req.query["element"]}';`);
|
||||||
|
if (remove.run()["changes"]) {
|
||||||
|
res.status(200).send();
|
||||||
|
} else {
|
||||||
|
res.status(404).send();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).send();
|
||||||
|
logger.error(error.message);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
res.status(400).send();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post("/app/upload", async (req, res) => {
|
||||||
|
if (!req.files || !req.files.file) {
|
||||||
|
res.status(422).send();
|
||||||
|
} else {
|
||||||
|
const uploadedFile = req.files.file;
|
||||||
|
|
||||||
|
db.close();
|
||||||
|
fs.copyFile(uploadedFile.tempFilePath, db_conf.path, (error) => {
|
||||||
|
if (error) {
|
||||||
|
logger.error(error);
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
db = new Database(db_conf.path, { fileMustExist: true });
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(error);
|
||||||
|
shutting_down = true;
|
||||||
|
shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
res.status(201).send();
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get("/app/download", async (req, res) => {
|
||||||
|
res.download(db_conf.path);
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post("/app/add", async (req, res) => {
|
||||||
|
if (req.query["element"] && req.body) {
|
||||||
|
try {
|
||||||
|
let keys = Object.keys(req.body);
|
||||||
|
let values = `'${req.query["element"]}',`;
|
||||||
|
|
||||||
|
for (const k of keys) {
|
||||||
|
if (req.body[k] === "null") {
|
||||||
|
values += `NULL,`
|
||||||
|
} else {
|
||||||
|
values += `'${req.body[k]}',`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (values) {
|
||||||
|
values = values.slice(0, -1);
|
||||||
|
const update = db.prepare(`INSERT INTO ${db_conf.table_name} VALUES (${values});`);
|
||||||
|
update.run();
|
||||||
|
res.status(200).send();
|
||||||
|
} else {
|
||||||
|
res.status(400).send();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).send();
|
||||||
|
logger.error(error.message);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
res.status(400).send();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post("/app/edit", async (req, res) => {
|
||||||
|
if (req.query["element"] && req.body) {
|
||||||
|
try {
|
||||||
|
let keys = Object.keys(req.body);
|
||||||
|
let values = "";
|
||||||
|
|
||||||
|
for (const k of keys) {
|
||||||
|
if (req.body[k] === "null") {
|
||||||
|
values += `${k}=NULL,`
|
||||||
|
} else {
|
||||||
|
values += `${k}='${req.body[k]}',`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (values) {
|
||||||
|
values = values.slice(0, -1);
|
||||||
|
const update = db.prepare(`UPDATE ${db_conf.table_name} SET ${values} WHERE Name='${req.query["element"]}'`);
|
||||||
|
update.run();
|
||||||
|
res.status(200).send();
|
||||||
|
} else {
|
||||||
|
res.status(400).send();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).send();
|
||||||
|
logger.error(error.message);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
res.status(400).send();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Main endpoint - might implement authentication step here...
|
||||||
|
*/
|
||||||
|
app.get("/", (req, res) => {
|
||||||
|
res.redirect("app/");
|
||||||
|
});
|
||||||
|
|
||||||
|
function errorPageRenderer(res, emoji, message) {
|
||||||
|
res.render("error", {
|
||||||
|
page_title: app_conf.name,
|
||||||
|
css_path: path.join(app_conf.web_paths.css, "stylesheet.css"),
|
||||||
|
error_emoji: emoji,
|
||||||
|
error_message: message
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
app.get("/app/add", async (req, res) => {
|
||||||
|
try {
|
||||||
|
const select_get = db.prepare(`SELECT * FROM ${db_conf.table_name};`);
|
||||||
|
const row = select_get.get();
|
||||||
|
|
||||||
|
const sql_header = Object.keys(row);
|
||||||
|
var table_header = `<th>${sql_header[0]}</th>\n`;
|
||||||
|
sql_header.splice(0, 1);
|
||||||
|
|
||||||
|
if (row) {
|
||||||
|
var column_options = {};
|
||||||
|
for (const key of sql_header) {
|
||||||
|
table_header += `<th>${key}</th>\n`;
|
||||||
|
|
||||||
|
const select_distinct = db.prepare(`
|
||||||
|
SELECT DISTINCT ${key}
|
||||||
|
FROM ${db_conf.table_name};
|
||||||
|
`);
|
||||||
|
values = select_distinct.all();
|
||||||
|
|
||||||
|
for (v of values) {
|
||||||
|
if (column_options[key] == null) {
|
||||||
|
column_options[key] = Object.values(v);
|
||||||
|
} else {
|
||||||
|
column_options[key] = column_options[key].concat(Object.values(v));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var table_data = "";
|
||||||
|
const sql_row_values = Object.values(row);
|
||||||
|
|
||||||
|
table_data += `<tr><td><input type="text" placeholder="Sensor name"></td>\n`;
|
||||||
|
for (let i = 1; i < sql_row_values.length; i++) {
|
||||||
|
table_data += `<td><select></select></td>\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
table_data += "</tr>";
|
||||||
|
res.render("add", {
|
||||||
|
page_title: app_conf.name,
|
||||||
|
css_path: path.join(app_conf.web_paths.css, "stylesheet.css"),
|
||||||
|
js_path: path.join(app_conf.web_paths.js, "add.js"),
|
||||||
|
column_options: JSON.stringify(column_options),
|
||||||
|
table_header: table_header,
|
||||||
|
table_data: table_data
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
errorPageRenderer(res, "❓", "The database is empty.");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(error.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Edit endpoint - edits a specific row
|
||||||
|
*/
|
||||||
|
app.get("/app/edit", async (req, res) => {
|
||||||
|
if (req.query["element"]) {
|
||||||
|
try {
|
||||||
|
const select_get = db.prepare(`SELECT * FROM ${db_conf.table_name} WHERE Name='${req.query["element"]}';`);
|
||||||
|
const row = select_get.get();
|
||||||
|
|
||||||
|
if (row) {
|
||||||
|
const sql_header = Object.keys(row);
|
||||||
|
var table_header = `<th>${sql_header[0]}</th>\n`;
|
||||||
|
sql_header.splice(0, 1);
|
||||||
|
|
||||||
|
var column_options = {};
|
||||||
|
for (const key of sql_header) {
|
||||||
|
table_header += `<th>${key}</th>\n`;
|
||||||
|
|
||||||
|
const select_distinct = db.prepare(`
|
||||||
|
SELECT DISTINCT ${key}
|
||||||
|
FROM ${db_conf.table_name}
|
||||||
|
WHERE ${key} IS NOT NULL
|
||||||
|
AND ${key} NOT IN (
|
||||||
|
SELECT ${key}
|
||||||
|
FROM ${db_conf.table_name}
|
||||||
|
WHERE Name='${req.query["element"]}'
|
||||||
|
AND ${key} IS NOT NULL);
|
||||||
|
`);
|
||||||
|
values = select_distinct.all();
|
||||||
|
|
||||||
|
for (v of values) {
|
||||||
|
if (column_options[key] == null) {
|
||||||
|
column_options[key] = Object.values(v);
|
||||||
|
} else {
|
||||||
|
column_options[key] = column_options[key].concat(Object.values(v));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var table_data = "";
|
||||||
|
const sql_row_values = Object.values(row);
|
||||||
|
table_data += `<tr><td>${sql_row_values[0]}</td>\n`;
|
||||||
|
sql_row_values.splice(0, 1);
|
||||||
|
|
||||||
|
for (const v of sql_row_values) {
|
||||||
|
table_data += `<td><select><option selected="selected">${v}</option></select></td>\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
table_data += "</tr>";
|
||||||
|
|
||||||
|
res.render("edit", {
|
||||||
|
page_title: app_conf.name,
|
||||||
|
css_path: path.join(app_conf.web_paths.css, "stylesheet.css"),
|
||||||
|
js_path: path.join(app_conf.web_paths.js, "edit.js"),
|
||||||
|
column_options: JSON.stringify(column_options),
|
||||||
|
table_header: table_header,
|
||||||
|
table_data: table_data
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
errorPageRenderer(res, "🔍❌", "Not found.");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(error.message);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
errorPageRenderer(res, "✋⛔", "Bad request.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
* App endpoint - shows the database
|
||||||
|
*/
|
||||||
|
app.get("/app", async (req, res) => {
|
||||||
|
logger.info(`${req.method}: "${req.url}" => ${req.get("User-Agent")}`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const select = db.prepare(`SELECT * FROM ${db_conf.table_name}`);
|
||||||
|
const rows = select.all();
|
||||||
|
|
||||||
|
if (rows[0]) {
|
||||||
|
const sql_header = Object.keys(rows[0]);
|
||||||
|
|
||||||
|
var table_header = "";
|
||||||
|
for (const key of sql_header) {
|
||||||
|
table_header += `<th>${key}</th>\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
var table_data = "";
|
||||||
|
for (const row of rows) {
|
||||||
|
const sql_row_values = Object.values(row);
|
||||||
|
table_data += "<tr>";
|
||||||
|
|
||||||
|
for (const v of sql_row_values) {
|
||||||
|
table_data += `<td>${v}</td>\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
table_data += "</tr>";
|
||||||
|
}
|
||||||
|
|
||||||
|
res.render("index", {
|
||||||
|
page_title: app_conf.name,
|
||||||
|
css_path: path.join(app_conf.web_paths.css, "stylesheet.css"),
|
||||||
|
js_path: path.join(app_conf.web_paths.js, "index.js"),
|
||||||
|
table_header: table_header,
|
||||||
|
table_data: table_data
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
errorPageRenderer(res, "❓", "The database is empty.");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
errorPageRenderer(res, "🌋💥", "Internal server error. Check logs for more information.");
|
||||||
|
logger.error(error.message);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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, () => {
|
||||||
|
logger.info("Starting Database Editor.");
|
||||||
|
logger.info("Connecting to Database...");
|
||||||
|
try {
|
||||||
|
db = new Database(db_conf.path, { fileMustExist: true });
|
||||||
|
logger.info("Connected to Database.");
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(error.message);
|
||||||
|
shutdown();
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
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;
|
||||||
|
logger.info("Closing Database connection.");
|
||||||
|
try {
|
||||||
|
db.close();
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(error.message);
|
||||||
|
}
|
||||||
|
server.close(() => {
|
||||||
|
logger.info("Shutting down server.");
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user