MySQL Editor in NodeJS
This is a short break from my Adventures of a Hobbyist series, where I'm learning node, to put some of what I've learnt into practice.
I'm not sure how long this mini-series will last but the aim is to make a web interface that lets you connect to a MySQL database to view and, if I get around to it, edit the tables.
Once I have a working prototype I'll put it up on GitHub and we'll see if anyone has any pull requests or issues to report.
Progress so far
So far I've put in the bare bones of the connection screen, this is loosely modelled on MySQL workbench but with a material twist.
Home screen:
Dialogue concept:
I use a JSON file to store the server details, not passwords, of course, to do this I'm using confHelper
which is something I wrote earlier, here's the post where I look at it. I'm also using expressjs
for my routing this time.
The code
I haven't really commented the code so far and I'm worried it will get out of hand fast. If you have any tips for commenting the code nicely I'm all ears.
Just to give you an idea of my super simple structure here's a picture.
As with all things with my projects if I'm doing something wrong let me know 😀
Server:
const express = require('express');
const app = express();
var path = require('path');
const ConfHelper = require('./conf_import');
const ch = new ConfHelper('servers');
app.get('/', function(req, res) {
res.sendFile(path.join(__dirname + '/pages/index.html'));
});
app.get('/styles/default.css', function(req, res) {
res.sendFile(path.join(__dirname + '/styles/default.css'));
});
app.get('/favicon.ico', function(req, res) {
res.sendFile(path.join(__dirname + '/pages/favicon.ico'));
});
app.get('/api/servers', function(req, res) {
res.json(ch.config.get().servers);
});
app.listen(80);
Homepage:
<head>
<title>nodetree</title>
<link rel="shortcut icon" type="image/x-icon" href="favicon.ico" />
<link rel="stylesheet" href="/styles/default.css">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<header><span class="blue">node</span><span class="orange">tree</span></header>
<nav>MySQL connections</nav>
<div class="fab shadow shadow-hover"> + </div>
<div class="box-container"></div>
<div hidden class="modal">
<div class="backdrop"></div>
<div class="content shadow">
<div class="header">Add new connection</div>
<div class="central"></div>
<div class="footer"><button class="modal-close">Cancel</button><button>Add</button></div>
</div>
</div>
</body>
<script>
const boxTemplate = (name, host, user) => {
// Main node
let node = document.createElement('div');
node.classList.add('boxes');
node.classList.add('shadow');
node.classList.add('shadow-hover');
// Div containing name
let nameNode = document.createElement('div');
nameNode.classList.add('name');
nameNode.appendChild(document.createTextNode(name));
node.appendChild(nameNode);
// Span containing host
let hostNode = document.createElement('span');
hostNode.appendChild(document.createTextNode(host));
node.appendChild(hostNode);
// Add break
let breakNode = document.createElement('br');
node.appendChild(breakNode);
// Span contain user
let userNode = document.createElement('span');
userNode.appendChild(document.createTextNode(`${user}@${host}`));
node.appendChild(userNode);
node.addEventListener('click', event => {
console.log(event.target.querySelector('.name').innerText);
})
return node
};
const closeModal = () => {
let modal = document.querySelector('.modal');
modal.hasAttribute('hidden') || modal.setAttribute('hidden','');
}
const fab = document.querySelector('.fab');
fab.addEventListener('click', event => {
let modal = document.querySelector('.modal');
modal.hasAttribute('hidden') && modal.removeAttribute('hidden');
})
const modalClose = document.querySelector('.modal-close');
modalClose.addEventListener('click', closeModal)
const modalBack = document.querySelector('.modal .backdrop');
modalBack.addEventListener('click', closeModal)
let oReq = new XMLHttpRequest();
oReq.addEventListener("load", _setBoxes);
oReq.open("GET", "/api/servers");
oReq.send()
function _setBoxes() {
const boxContainer = document.querySelector('.box-container');
const res = JSON.parse(this.response);
let servers = Object.keys(res);
for (let server of servers) {
boxContainer.appendChild(boxTemplate(server, res[server].host, res[server].user));
}
}
</script>
Styles:
@import url('https://fonts.googleapis.com/css?family=Open+Sans');
:root {
--header-font-size: 12.5vh;
--header-height: 25vh;
--nav-height: 60px;
--fab-size: var(--nav-height);
--box-flex: flex-start;
--modal-size: 80;
--modal-radius: 6px;
}
@media only screen and (min-device-width : 768px) and (max-device-width : 1024px) {
:root {
--header-font-size: 60px;
--header-height: 15vh;
--nav-height: 60px;
--box-flex: flex-start;
--modal-size: 80;
--modal-radius: 6px;
}
}
@media only screen and (max-device-width : 768px) {
:root {
--header-font-size: 60px;
--header-height: 15vh;
--nav-height: 50px;
--box-flex: space-around;
--modal-size: 100;
--modal-radius: 0px;
}
}
body {
background: #e2e1e0;
font-family: 'Open Sans';
margin: 0;
}
header {
height: var(--header-height);
width: 100vw;
background: white;
text-align: center;
line-height: var(--header-height);
font-size: var(--header-font-size);
}
.blue {
color: #303F9F;
}
.orange {
color: #FF5722;
}
nav {
padding: 0px 25px;
background: #3F51B5;
height: var(--nav-height);
line-height: var(--nav-height);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
color: #fff;
}
nav button.add {
border-radius: 12px;
background: rgba(0, 0, 0, 0);
height: 24px;
width: 24px;
box-sizing: border-box;
color: #fff;
border: #fff solid 1px;
}
.fab {
position: relative;
cursor: pointer;
text-align: center;
background-color: #FF5722;
color: white;
/* calculated from --fab-size */
top: calc((var(--fab-size) / 2) * -1);
width: var(--fab-size);
height: var(--fab-size);
margin-bottom: calc(var(--fab-size) * -1);
margin-left: calc(100vw - var(--fab-size) - 25px);
border-radius: calc(var(--fab-size) / 2);
font-size: calc((var(--fab-size) / 8) * 5);
line-height: var(--fab-size);
}
.shadow {
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
transition: box-shadow 0.3s cubic-bezier(.25, .8, .25, 1);
}
.shadow-hover:hover {
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.19), 0 6px 6px rgba(0, 0, 0, 0.23);
}
.box-container {
display: flex;
justify-content: var(--box-flex);
flex-wrap: wrap;
padding: 25px;
}
.boxes {
cursor: pointer;
min-width: 225px;
height: 90px;
border-radius: 2px;
background: #fff;
color: #757575;
padding: 0 0 10px 10px;
margin: 10px;
}
.boxes .name {
padding: 5px 0 10px 0;
font-size: 1.5em;
color: #212121;
pointer-events: none;
}
.boxes span {
pointer-events: none;
}
.modal {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
}
.modal .backdrop {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background: rgba(46,46,46,0.6);
}
.modal .content {
position: fixed;
top: calc((100vh - (var(--modal-size) * 1vh))/2);
left: calc((100vw - (var(--modal-size) * 1vw))/2);
width: calc((var(--modal-size) * 1vw));
height: calc((var(--modal-size) * 1vh));
border-radius: var(--modal-radius);
background: #fff;
color: #757575;
}
.modal .content .header {
height: 50px;
line-height: 50px;
color: #212121;
border-bottom: 1px solid #BDBDBD;
font-size: 34px;
text-align: center;
}
.modal .content .central {
height: calc((var(--modal-size) * 1vh) - 102px);
color: #C5CAE9;
}
.modal .content .footer {
height: 50px;
line-height: 50px;
color: #303F9F;
border-top: 1px solid #BDBDBD;
padding: 0 23px;
text-align: right;
}
.modal .content .footer button {
cursor: pointer;
color: #303F9F;
border: none;
background: none;
height: 50px;
font-size: 18px;
}
The end
So that's as far as I got today, I have a long to-do list you may have noticed this version doesn't even interact with any MySQL stuff yet. It's a long journey, thank you for coming along with me.
Thanks again 🦄🦄🦄