diff --git a/README.md b/README.md index 2a1a49d..003fd5c 100644 --- a/README.md +++ b/README.md @@ -4,12 +4,29 @@ rcn-frontserver About ---- This is the front-web-server of the Raspberry pi Communication network. -More details following soon. + +Modules +---- +**NodeJS** +- winston +- pkg +- perfy +- jsdom +- winston-daily-rotate-file +- args-parser + +**Embedded in the html-file** +- JQuery +- Vue.js + +Details +---- +This web server uses folders for every type of file (depending on the file's ending) and routes to the folders. There are also global files, that are implemented in every html via dom-manipulation. The behaviour for every file extension can be configured in the config.json This server uses vue.js and jquery to make the website as interactive, as possible. This server works with https. To run it. it needs to have access to the folder .ssh in the same directory where a valid cert.pem and a valid key.pem is are existing. Roadmap ---- **Done** **ToDo** -- Mounting of folders or files on other locations -- All important files for the client +- Mounting of folders or files on other locations (by using the config.json) +- a package.json because it seems to be important nowadays diff --git a/config.json b/config.json new file mode 100644 index 0000000..a59601e --- /dev/null +++ b/config.json @@ -0,0 +1,40 @@ +{ + "routes": { + ".html": { + "path": "./res/html/", + "mime": "text/html" + }, + ".htm": { + "path": "./res/html/", + "mime": "text/html" + }, + ".js": { + "path": "./res/scripts/", + "mime": "text/javascript" + }, + ".css": { + "path": "./res/css/", + "mime": "text/css" + }, + ".json": { + "path": "./res/data/", + "mime": "text/plain" + }, + ".ico": { + "path": "./res/img/", + "mime": "image/x-icon" + }, + ".jpg": { + "path": "./res/img/", + "mime": "image/jpeg" + }, + ".png": { + "path": "./res/img/", + "mime": "image/png" + }, + ".ttf": { + "path": "./res/fonts/", + "mime": "font/opentype" + } + } +} diff --git a/glob/style.css b/glob/style.css index e69de29..8247de9 100644 --- a/glob/style.css +++ b/glob/style.css @@ -0,0 +1,16 @@ +@font-face { + font-family: ubuntuL; + src: url(/Ubuntu-L.ttf); +} +@font-face { + font-family: ubuntuR; + src: url(/Ubuntu-R.ttf); +} +@font-face { + font-family: ubuntuMono; + src: url(/UbuntuMono-R.ttf); +} + +body { + font-family: ubuntuL; +} diff --git a/glob/vue.js b/glob/vue.js new file mode 100644 index 0000000..88d54a4 --- /dev/null +++ b/glob/vue.js @@ -0,0 +1,6 @@ +/*! + * Vue.js v2.5.17 + * (c) 2014-2018 Evan You + * Released under the MIT License. + */ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.Vue=t()}(this,function(){"use strict";var y=Object.freeze({});function M(e){return null==e}function D(e){return null!=e}function S(e){return!0===e}function T(e){return"string"==typeof e||"number"==typeof e||"symbol"==typeof e||"boolean"==typeof e}function P(e){return null!==e&&"object"==typeof e}var r=Object.prototype.toString;function l(e){return"[object Object]"===r.call(e)}function i(e){var t=parseFloat(String(e));return 0<=t&&Math.floor(t)===t&&isFinite(e)}function t(e){return null==e?"":"object"==typeof e?JSON.stringify(e,null,2):String(e)}function F(e){var t=parseFloat(e);return isNaN(t)?e:t}function s(e,t){for(var n=Object.create(null),r=e.split(","),i=0;ie.id;)n--;bt.splice(n+1,0,e)}else bt.push(e);Ct||(Ct=!0,Ze(At))}}(this)},St.prototype.run=function(){if(this.active){var e=this.get();if(e!==this.value||P(e)||this.deep){var t=this.value;if(this.value=e,this.user)try{this.cb.call(this.vm,e,t)}catch(e){Fe(e,this.vm,'callback for watcher "'+this.expression+'"')}else this.cb.call(this.vm,e,t)}}},St.prototype.evaluate=function(){this.value=this.get(),this.dirty=!1},St.prototype.depend=function(){for(var e=this.deps.length;e--;)this.deps[e].depend()},St.prototype.teardown=function(){if(this.active){this.vm._isBeingDestroyed||f(this.vm._watchers,this);for(var e=this.deps.length;e--;)this.deps[e].removeSub(this);this.active=!1}};var Tt={enumerable:!0,configurable:!0,get:$,set:$};function Et(e,t,n){Tt.get=function(){return this[t][n]},Tt.set=function(e){this[t][n]=e},Object.defineProperty(e,n,Tt)}function jt(e){e._watchers=[];var t=e.$options;t.props&&function(n,r){var i=n.$options.propsData||{},o=n._props={},a=n.$options._propKeys=[];n.$parent&&ge(!1);var e=function(e){a.push(e);var t=Ie(e,r,i,n);Ce(o,e,t),e in n||Et(n,"_props",e)};for(var t in r)e(t);ge(!0)}(e,t.props),t.methods&&function(e,t){e.$options.props;for(var n in t)e[n]=null==t[n]?$:v(t[n],e)}(e,t.methods),t.data?function(e){var t=e.$options.data;l(t=e._data="function"==typeof t?function(e,t){se();try{return e.call(t,t)}catch(e){return Fe(e,t,"data()"),{}}finally{ce()}}(t,e):t||{})||(t={});var n=Object.keys(t),r=e.$options.props,i=(e.$options.methods,n.length);for(;i--;){var o=n[i];r&&p(r,o)||(void 0,36!==(a=(o+"").charCodeAt(0))&&95!==a&&Et(e,"_data",o))}var a;we(t,!0)}(e):we(e._data={},!0),t.computed&&function(e,t){var n=e._computedWatchers=Object.create(null),r=Y();for(var i in t){var o=t[i],a="function"==typeof o?o:o.get;r||(n[i]=new St(e,a||$,$,Nt)),i in e||Lt(e,i,o)}}(e,t.computed),t.watch&&t.watch!==G&&function(e,t){for(var n in t){var r=t[n];if(Array.isArray(r))for(var i=0;iparseInt(this.max)&&bn(a,s[0],s,this._vnode)),t.data.keepAlive=!0}return t||e&&e[0]}}};$n=hn,Cn={get:function(){return j}},Object.defineProperty($n,"config",Cn),$n.util={warn:re,extend:m,mergeOptions:Ne,defineReactive:Ce},$n.set=xe,$n.delete=ke,$n.nextTick=Ze,$n.options=Object.create(null),k.forEach(function(e){$n.options[e+"s"]=Object.create(null)}),m(($n.options._base=$n).options.components,kn),$n.use=function(e){var t=this._installedPlugins||(this._installedPlugins=[]);if(-1=a&&l()};setTimeout(function(){c\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/,oo="[a-zA-Z_][\\w\\-\\.]*",ao="((?:"+oo+"\\:)?"+oo+")",so=new RegExp("^<"+ao),co=/^\s*(\/?)>/,lo=new RegExp("^<\\/"+ao+"[^>]*>"),uo=/^]+>/i,fo=/^",""":'"',"&":"&"," ":"\n"," ":"\t"},go=/&(?:lt|gt|quot|amp);/g,_o=/&(?:lt|gt|quot|amp|#10|#9);/g,bo=s("pre,textarea",!0),$o=function(e,t){return e&&bo(e)&&"\n"===t[0]};var wo,Co,xo,ko,Ao,Oo,So,To,Eo=/^@|^v-on:/,jo=/^v-|^@|^:/,No=/([^]*?)\s+(?:in|of)\s+([^]*)/,Lo=/,([^,\}\]]*)(?:,([^,\}\]]*))?$/,Io=/^\(|\)$/g,Mo=/:(.*)$/,Do=/^:|^v-bind:/,Po=/\.[^.]+/g,Fo=e(eo);function Ro(e,t,n){return{type:1,tag:e,attrsList:t,attrsMap:function(e){for(var t={},n=0,r=e.length;n]*>)","i")),n=i.replace(t,function(e,t,n){return r=n.length,ho(o)||"noscript"===o||(t=t.replace(//g,"$1").replace(//g,"$1")),$o(o,t)&&(t=t.slice(1)),d.chars&&d.chars(t),""});a+=i.length-n.length,i=n,A(o,a-r,a)}else{var s=i.indexOf("<");if(0===s){if(fo.test(i)){var c=i.indexOf("--\x3e");if(0<=c){d.shouldKeepComment&&d.comment(i.substring(4,c)),C(c+3);continue}}if(po.test(i)){var l=i.indexOf("]>");if(0<=l){C(l+2);continue}}var u=i.match(uo);if(u){C(u[0].length);continue}var f=i.match(lo);if(f){var p=a;C(f[0].length),A(f[1],p,a);continue}var _=x();if(_){k(_),$o(v,i)&&C(1);continue}}var b=void 0,$=void 0,w=void 0;if(0<=s){for($=i.slice(s);!(lo.test($)||so.test($)||fo.test($)||po.test($)||(w=$.indexOf("<",1))<0);)s+=w,$=i.slice(s);b=i.substring(0,s),C(s)}s<0&&(b=i,i=""),d.chars&&b&&d.chars(b)}if(i===e){d.chars&&d.chars(i);break}}function C(e){a+=e,i=i.substring(e)}function x(){var e=i.match(so);if(e){var t,n,r={tagName:e[1],attrs:[],start:a};for(C(e[0].length);!(t=i.match(co))&&(n=i.match(io));)C(n[0].length),r.attrs.push(n);if(t)return r.unarySlash=t[1],C(t[0].length),r.end=a,r}}function k(e){var t=e.tagName,n=e.unarySlash;m&&("p"===v&&ro(t)&&A(v),g(t)&&v===t&&A(t));for(var r,i,o,a=y(t)||!!n,s=e.attrs.length,c=new Array(s),l=0;l-1"+("true"===d?":("+l+")":":_q("+l+","+d+")")),Ar(c,"change","var $$a="+l+",$$el=$event.target,$$c=$$el.checked?("+d+"):("+v+");if(Array.isArray($$a)){var $$v="+(f?"_n("+p+")":p)+",$$i=_i($$a,$$v);if($$el.checked){$$i<0&&("+Er(l,"$$a.concat([$$v])")+")}else{$$i>-1&&("+Er(l,"$$a.slice(0,$$i).concat($$a.slice($$i+1))")+")}}else{"+Er(l,"$$c")+"}",null,!0);else if("input"===$&&"radio"===w)r=e,i=_,a=(o=b)&&o.number,s=Or(r,"value")||"null",Cr(r,"checked","_q("+i+","+(s=a?"_n("+s+")":s)+")"),Ar(r,"change",Er(i,s),null,!0);else if("input"===$||"textarea"===$)!function(e,t,n){var r=e.attrsMap.type,i=n||{},o=i.lazy,a=i.number,s=i.trim,c=!o&&"range"!==r,l=o?"change":"range"===r?Pr:"input",u="$event.target.value";s&&(u="$event.target.value.trim()"),a&&(u="_n("+u+")");var f=Er(t,u);c&&(f="if($event.target.composing)return;"+f),Cr(e,"value","("+t+")"),Ar(e,l,f,null,!0),(s||a)&&Ar(e,"blur","$forceUpdate()")}(e,_,b);else if(!j.isReservedTag($))return Tr(e,_,b),!1;return!0},text:function(e,t){t.value&&Cr(e,"textContent","_s("+t.value+")")},html:function(e,t){t.value&&Cr(e,"innerHTML","_s("+t.value+")")}},isPreTag:function(e){return"pre"===e},isUnaryTag:to,mustUseProp:Sn,canBeLeftOpenTag:no,isReservedTag:Un,getTagNamespace:Vn,staticKeys:(Go=Wo,Go.reduce(function(e,t){return e.concat(t.staticKeys||[])},[]).join(","))},Qo=e(function(e){return s("type,tag,attrsList,attrsMap,plain,parent,children,attrs"+(e?","+e:""))});function ea(e,t){e&&(Zo=Qo(t.staticKeys||""),Xo=t.isReservedTag||O,function e(t){t.static=function(e){if(2===e.type)return!1;if(3===e.type)return!0;return!(!e.pre&&(e.hasBindings||e.if||e.for||c(e.tag)||!Xo(e.tag)||function(e){for(;e.parent;){if("template"!==(e=e.parent).tag)return!1;if(e.for)return!0}return!1}(e)||!Object.keys(e).every(Zo)))}(t);if(1===t.type){if(!Xo(t.tag)&&"slot"!==t.tag&&null==t.attrsMap["inline-template"])return;for(var n=0,r=t.children.length;n|^function\s*\(/,na=/^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*|\['[^']*?']|\["[^"]*?"]|\[\d+]|\[[A-Za-z_$][\w$]*])*$/,ra={esc:27,tab:9,enter:13,space:32,up:38,left:37,right:39,down:40,delete:[8,46]},ia={esc:"Escape",tab:"Tab",enter:"Enter",space:" ",up:["Up","ArrowUp"],left:["Left","ArrowLeft"],right:["Right","ArrowRight"],down:["Down","ArrowDown"],delete:["Backspace","Delete"]},oa=function(e){return"if("+e+")return null;"},aa={stop:"$event.stopPropagation();",prevent:"$event.preventDefault();",self:oa("$event.target !== $event.currentTarget"),ctrl:oa("!$event.ctrlKey"),shift:oa("!$event.shiftKey"),alt:oa("!$event.altKey"),meta:oa("!$event.metaKey"),left:oa("'button' in $event && $event.button !== 0"),middle:oa("'button' in $event && $event.button !== 1"),right:oa("'button' in $event && $event.button !== 2")};function sa(e,t,n){var r=t?"nativeOn:{":"on:{";for(var i in e)r+='"'+i+'":'+ca(i,e[i])+",";return r.slice(0,-1)+"}"}function ca(t,e){if(!e)return"function(){}";if(Array.isArray(e))return"["+e.map(function(e){return ca(t,e)}).join(",")+"]";var n=na.test(e.value),r=ta.test(e.value);if(e.modifiers){var i="",o="",a=[];for(var s in e.modifiers)if(aa[s])o+=aa[s],ra[s]&&a.push(s);else if("exact"===s){var c=e.modifiers;o+=oa(["ctrl","shift","alt","meta"].filter(function(e){return!c[e]}).map(function(e){return"$event."+e+"Key"}).join("||"))}else a.push(s);return a.length&&(i+="if(!('button' in $event)&&"+a.map(la).join("&&")+")return null;"),o&&(i+=o),"function($event){"+i+(n?"return "+e.value+"($event)":r?"return ("+e.value+")($event)":e.value)+"}"}return n||r?e.value:"function($event){"+e.value+"}"}function la(e){var t=parseInt(e,10);if(t)return"$event.keyCode!=="+t;var n=ra[e],r=ia[e];return"_k($event.keyCode,"+JSON.stringify(e)+","+JSON.stringify(n)+",$event.key,"+JSON.stringify(r)+")"}var ua={on:function(e,t){e.wrapListeners=function(e){return"_g("+e+","+t.value+")"}},bind:function(t,n){t.wrapData=function(e){return"_b("+e+",'"+t.tag+"',"+n.value+","+(n.modifiers&&n.modifiers.prop?"true":"false")+(n.modifiers&&n.modifiers.sync?",true":"")+")"}},cloak:$},fa=function(e){this.options=e,this.warn=e.warn||$r,this.transforms=wr(e.modules,"transformCode"),this.dataGenFns=wr(e.modules,"genData"),this.directives=m(m({},ua),e.directives);var t=e.isReservedTag||O;this.maybeComponent=function(e){return!t(e.tag)},this.onceId=0,this.staticRenderFns=[]};function pa(e,t){var n=new fa(t);return{render:"with(this){return "+(e?da(e,n):'_c("div")')+"}",staticRenderFns:n.staticRenderFns}}function da(e,t){if(e.staticRoot&&!e.staticProcessed)return va(e,t);if(e.once&&!e.onceProcessed)return ha(e,t);if(e.for&&!e.forProcessed)return f=t,v=(u=e).for,h=u.alias,m=u.iterator1?","+u.iterator1:"",y=u.iterator2?","+u.iterator2:"",u.forProcessed=!0,(d||"_l")+"(("+v+"),function("+h+m+y+"){return "+(p||da)(u,f)+"})";if(e.if&&!e.ifProcessed)return ma(e,t);if("template"!==e.tag||e.slotTarget){if("slot"===e.tag)return function(e,t){var n=e.slotName||'"default"',r=_a(e,t),i="_t("+n+(r?","+r:""),o=e.attrs&&"{"+e.attrs.map(function(e){return g(e.name)+":"+e.value}).join(",")+"}",a=e.attrsMap["v-bind"];!o&&!a||r||(i+=",null");o&&(i+=","+o);a&&(i+=(o?"":",null")+","+a);return i+")"}(e,t);var n;if(e.component)a=e.component,c=t,l=(s=e).inlineTemplate?null:_a(s,c,!0),n="_c("+a+","+ya(s,c)+(l?","+l:"")+")";else{var r=e.plain?void 0:ya(e,t),i=e.inlineTemplate?null:_a(e,t,!0);n="_c('"+e.tag+"'"+(r?","+r:"")+(i?","+i:"")+")"}for(var o=0;o':'
',0 { - return `${info.timestamp} ${info.level.toUpperCase()}: ${JSON.stringify(info.message)}`; - }); + return `${info.timestamp} ${info.level.toUpperCase()}: ${JSON.stringify(info.message)}`; // the logging format for files + }), consoleLoggingFormat = winston.format.printf(info => { - return `${info.timestamp} [${info.level}] ${JSON.stringify(info.message)}`; - }); + return `${info.timestamp} [${info.level}] ${JSON.stringify(info.message)}`; //the logging format for the console + }), loggingFullFormat = winston.format.combine( winston.format.splat(), winston.format.timestamp({ - format: 'MM-DD HH:mm:ss.SSS' + format: 'MM-DD HH:mm:ss.SSS' // don't include the year because the filename already tells }), - fileLoggingFormat + fileLoggingFormat // the logging format for files that logs with a capitalized level ), logger = winston.createLogger({ - level: winston.config.npm.levels, - format: loggingFullFormat, + level: winston.config.npm.levels, // logs with npm levels + format: loggingFullFormat, // the full format for files transports: [ new winston.transports.Console({ format: winston.format.combine( - winston.format.colorize(), + winston.format.colorize(), // colorizes the console logging output winston.format.splat(), winston.format.timestamp({ - format: 'YY-MM-DD HH:mm:ss.SSS' + format: 'YY-MM-DD HH:mm:ss.SSS' // logs with the year to the console }), - consoleLoggingFormat + consoleLoggingFormat // logs with the custom console format ), - level: args.loglevel || 'info' + level: args.loglevel || 'info' // logs to the console with the arg loglevel or info if it is not given }), new winston.transports.File({ - level: 'debug', - filename: './.log/rcn-frontserver.log' + level: 'debug', // logs with debug level to the active file + filename: './.log/rcn-frontserver.log', // the filename of the current file, + options: {flags: 'w'} // overwrites the file on restart }), new DailyRotateFile({ - level: 'verbose', - filename: './.log/frontserver-%DATE%.log', - datePattern: 'YYYY-MM-DD', - zippedArchive: true, - maxSize: '32m', - maxFiles: '30d' + level: 'verbose', // log verbose in the rotating logvile + filename: './.log/frontserver-%DATE%.log', // the pattern of the filename + datePattern: 'YYYY-MM-DD', // the pattern of %DATE% + zippedArchive: true, // indicates that old logfiles should get zipped + maxSize: '32m', // the maximum filesize + maxFiles: '30d' // the maximum files to keep }) ] }), @@ -59,7 +62,18 @@ const https = require('https'), key: fs.readFileSync('.ssh/key.pem'), // the key-file cert: fs.readFileSync('.ssh/cert.pem') // the certificate-file }, - port = args.port || 80; // The port of the web server. + port = args.port || 80, + routes = config.routes || { + ".html": { + "path": "./res/html", + "mime": "text/html" + }, + ".js": { + "path": "./res/scripts", + "mime": "text/javascript" + } + } + ; // The port of the web server. // --- functional declaration part --- @@ -110,12 +124,12 @@ function getExtension(filename) { } /** - * Returns a string that depends on the path. + * Returns a string that depends on the path. It gets the data from the routes variable. * @param {String} path Normally a file-name. Depending on the extension, an other root-path is choosen. * @return {String} An Array containing (Either the files content or an error message) and the mime-type. */ function getResponse(path) { - logger.verbose({'msg':'calculationg response', 'path': path}); + logger.verbose({'msg':'calculating response', 'path': path}); try { // get the file extension let extension = getExtension(path); @@ -126,39 +140,47 @@ function getResponse(path) { if (extension == ".css") return [fs.readFileSync("." + path), "text/css"]; else return [fs.readFileSync("." + path), "text/javascript"]; } + let route = routes[extension]; + if (!route) return ["Not Allowed", "text/plain"]; + let rf = fs.readFileSync; + if (extension == ".html") return [formatHtml(rf(route["path"]+path)), route["mime"]]; + return [rf(route["path"]+path), route["mime"]]; // test the extension for differend file types. - switch (extension) { - case '.html': - case '.htm': - return [formatHtml(fs.readFileSync("./res/html/" + path)), "text/html"]; - case '.css': - return [fs.readFileSync("./res/css/"+path), "text/css"]; - case '.js': - return [fs.readFileSync("./res/scripts/"+path), "text/javascript"]; - case '.json': - return [fs.readFileSync("./res/data/"+path), "text/plain"]; - // return some images - case '.ico': - return [fs.readFileSync("./res/img/"+path), "image/x-icon"] - case '.jpg': - return [fs.readFileSync("./res/img/"+path), "image/jpeg"]; - case '.png': - return [fs.readFileSync("./res/img/"+path), "image/png"]; - // return some videos - case '.mp4': - return [fs.readFileSync("./res/vid/"+path), "video/mpeg"]; - // default return - default: - // if the extension is not in those above, the access is not allowed. - logger.verbose({'msg': 'Illegal request', 'path': path}) - return ["Not Allowed", "text/plain"]; - } + logger.verbose({'msg': 'Error', 'path': path}) + return ["Error with url", "text/plain"]; } catch (error) { logger.error(error); return "Error"; } } +/** + * Creates a css DOM element with href as source + * @param {Object} document A html DOM + * @param {String} href the source of the css file + * @return {Object} the Link Element + */ +function createLinkElement(document, href) { + let link = document.createElement('link'); + link.setAttribute("rel", "stylesheet"); + link.setAttribute("type", "text/css"); + link.setAttribute("href", href); + return link; +} + +/** + * Creates a javascript Script DOM element with src as source + * @param {Object} document A html DOM + * @param {String} src the source of the javascript file + * @return {Object} the Script Element + */ +function createScriptLinkElement(document, src) { + let script = document.createElement("script"); + script.setAttribute("type", "text/javascript"); + script.setAttribute("src", src); + return script; +} + /** * Formats the html string by adding a link to the standard css and to the standard javascript file. * @param {String} htmlstring A string read from an html file or a html document string itself. @@ -170,19 +192,10 @@ function formatHtml(htmlstring) { let dom = new JSDOM(htmlstring); // creates a dom from the html string let document = dom.window.document; let head = document.getElementsByTagName('head')[0]; // gets the documents head - let css = document.createElement('link'); // create a link for the css embeding - css.setAttribute("rel", "stylesheet"); - css.setAttribute("type", "text/css"); - css.setAttribute("href", defaultCss); - head.prepend(css); // prepend the link to the head - let js = document.createElement('script'); // create a script for js embedding - js.setAttribute("type", "text/javascript"); - js.setAttribute("src", defaultJs); - head.prepend(js); // prepend the script to the head - let jquery = document.createElement('script'); // create a script for js embedding - jquery.setAttribute("type", "text/javascript"); - jquery.setAttribute("src", "/glob/jquery.js"); - head.prepend(jquery); // prepend the script to the head + head.prepend(createLinkElement(document, defaultCss)); // prepend the default css to the head + head.prepend(createScriptLinkElement(document, defaultJs)); // prepend the default script to the head + head.prepend(createScriptLinkElement(document, "/glob/jquery.js")); // prepend the JQuery to the head + head.prepend(createScriptLinkElement(document, "/glob/vue.js")); // prepend the Vue to the head return dom.serialize(); // return a string of the document } catch(error) { logger.error(error);