From 5bedb0c2a9597332b70e62e39ad3d58c8b926985 Mon Sep 17 00:00:00 2001 From: alban Date: Sat, 23 May 2020 21:47:49 +0200 Subject: [PATCH 1/4] [fix] simplify css --- public/css/site.css | 53 +++++++++++++++++++++------------------------ 1 file changed, 25 insertions(+), 28 deletions(-) diff --git a/public/css/site.css b/public/css/site.css index c4f874c..85e1947 100644 --- a/public/css/site.css +++ b/public/css/site.css @@ -5,45 +5,42 @@ */ .log { - margin-bottom: 10px; - border-top: 1px solid #eee; - padding-top: 6px; -} -.log p { - color: #666; -} -.log span.cmd { - background: #eee; - color: #003e80; - padding: 3px 8px; -} -.log pre { - font-size: 1.0rem; - color: #333; + padding: 16px 0; + border-top: 1px solid #eee; } -.log .meta a { - color: #666; +.log .meta { font-size: .875rem; + color: #888; + line-height:1.2em; } + .log .meta p { - font-size: .875rem; - line-height:1em; - margin: 0px; - overflow: hidden; - text-overflow: ellipsis; + margin: 0px; + overflow: hidden; + text-overflow: ellipsis; } .log .meta p.server { - color:#333; + color: #333; + font-size:1.125rem; } .log .meta .actions-toggle { - margin: 0; - padding: 0; - color: #666; - cursor: pointer; - line-height:1em; + margin: 0; + padding: 0; + cursor: pointer; + line-height:1em; } .log .meta .actions{ display:none; } + +.log pre { + font-size: 1.125rem; + color: #333; +} +.log pre .cmd { + background: #eee; + color: #003e80; + padding: 3px 8px; +} From 3ce2e4514df8216502a9cac9d227ab4db68e8a54 Mon Sep 17 00:00:00 2001 From: alban Date: Sat, 23 May 2020 22:06:24 +0200 Subject: [PATCH 2/4] [fix] tweaks --- dbInit/index.js | 6 +++--- public/css/site.css | 9 ++++++++- public/js/app.js | 24 ++++++++++++++---------- 3 files changed, 25 insertions(+), 14 deletions(-) diff --git a/dbInit/index.js b/dbInit/index.js index 7db0768..afa258a 100644 --- a/dbInit/index.js +++ b/dbInit/index.js @@ -6,17 +6,17 @@ const dbInit = {}; const bulkData = [ { "index" : { "_index" : "changelog" } }, - { "author" : "John Ripper ", "content":"* machines: Installed the server\n```debootstrap -t foobar```", "server": "server.example.com", "created_at":"2020-05-23T09:50:33.397Z"}, { "index" : { "_index" : "changelog" } }, - { "author" : "John Ripper ", "content":"* db: Installed mysql\n```apt install mariadb-server```", "server": "server.example.com", "created_at":"2020-05-23T10:50:33.397Z"}, { "index" : { "_index" : "changelog" } }, - { "author" : "John Ripper ", "content":"* nginx: add package\n```apt install nginx-full```", "server": "server.example.com", "created_at":"2020-05-23T16:50:33.397Z"} diff --git a/public/css/site.css b/public/css/site.css index 85e1947..10f4a7c 100644 --- a/public/css/site.css +++ b/public/css/site.css @@ -13,6 +13,8 @@ font-size: .875rem; color: #888; line-height:1.2em; + text-align: right; + } .log .meta p { @@ -41,6 +43,11 @@ } .log pre .cmd { background: #eee; - color: #003e80; + color: #333f4d; padding: 3px 8px; } +@media (max-width: 991.98px){ +.log .meta { + text-align: left; +} + diff --git a/public/js/app.js b/public/js/app.js index 0631fbe..5a1a728 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -21,20 +21,24 @@ escape = function(string) { }; var urlRegex = /(\S+): (https?://[^\s]+)/g; url = function(string){ - return ''+string.replace(urlRegex, '$1') -} + return ''+string.replace(urlRegex, '$1'); +}; var titleRegex = /^(.*\n)/; title = function(string){ return ''+string.replace(titleRegex, '$1'); -} -var cmdRegex = /```([^`]*?)```/g +}; +var cmdRegex = /```([^`]*?)```/g; cmd = function(string) { return ''+string.replace(cmdRegex, '$1'); -} -date = function(date){ - var D = new Date(date); +}; +date = function(string){ + var D = new Date(string); return D.toLocaleDateString()+" "+D.toLocaleTimeString(); -} +}; +mailRegexp = /(.*) <(.+@.+)>/; +mail = function( string ){ + return ''+string.replace(mailRegexp, '$1'); +}; function updatePage(data){ var content = ""; @@ -51,13 +55,13 @@ function updatePage(data){ content += `
-
+

${escape(item.server)}

${date(escape(item.created_at))}
-

${escape(item.author)}

+

${mail(escape(item.author))}

Actions

From b0c604374111ba75b933708029797a7fa96e27ad Mon Sep 17 00:00:00 2001 From: alban Date: Sat, 23 May 2020 23:26:51 +0200 Subject: [PATCH 3/4] [enh] The delete action should work --- index.js | 4 +- public/js/app.js | 219 ++++++++++++++++++++++++++--------------------- routes/index.js | 47 ++++++++++ 3 files changed, 168 insertions(+), 102 deletions(-) diff --git a/index.js b/index.js index b453b21..a63b304 100644 --- a/index.js +++ b/index.js @@ -29,8 +29,6 @@ dbInit.init({ seed : process.env.DB_SEED }); -console.log( "exit") - const express = require('express'); const app = express(); @@ -71,7 +69,7 @@ app.post('/*', routes.add); app.get('/*', routes.main); app.patch('/*', routes.main); app.put('/*', routes.main); -app.delete('/*', routes.main); +app.delete('/delete/:id', routes.delete); app.listen(port, () => { diff --git a/public/js/app.js b/public/js/app.js index 5a1a728..7838bd4 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -1,107 +1,128 @@ /* global initData, authorizationToken */ -// List of HTML entities for escaping. -var htmlEscapes = { - '&': '&', - '<': '<', - '>': '>', - '"': '"', - "'": ''', - '/': '/' -}; +$(function(){ + + // List of HTML entities for escaping. + var htmlEscapes = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + '/': '/' + }; -// Regex containing the keys listed immediately above. -var htmlEscaper = /[&<>"'\/]/g; + // Regex containing the keys listed immediately above. + var htmlEscaper = /[&<>"'\/]/g; -// Escape a string for HTML interpolation. -escape = function(string) { - return ('' + string).replace(htmlEscaper, function(match) { - return htmlEscapes[match]; - }); -}; -var urlRegex = /(\S+): (https?://[^\s]+)/g; -url = function(string){ - return ''+string.replace(urlRegex, '$1'); -}; -var titleRegex = /^(.*\n)/; -title = function(string){ - return ''+string.replace(titleRegex, '$1'); -}; -var cmdRegex = /```([^`]*?)```/g; -cmd = function(string) { - return ''+string.replace(cmdRegex, '$1'); -}; -date = function(string){ - var D = new Date(string); - return D.toLocaleDateString()+" "+D.toLocaleTimeString(); -}; -mailRegexp = /(.*) <(.+@.+)>/; -mail = function( string ){ - return ''+string.replace(mailRegexp, '$1'); -}; -function updatePage(data){ - - var content = ""; - var item = {}; - var id = ''; - // If the log entry is unique, simulate a search result - if( ! data['hits'] ){ - data = {hits:{hits:[data]}}; - } - $.each(data.hits.hits, (k,v)=>{ - - item = v._source; - id = v._id; - content += ` + // Escape a string for HTML interpolation. + escape = function(string) { + return ('' + string).replace(htmlEscaper, function(match) { + return htmlEscapes[match]; + }); + }; + var urlRegex = /(\S+): (https?://[^\s]+)/g; + url = function(string){ + return ''+string.replace(urlRegex, '$1'); + }; + var titleRegex = /^(.*\n)/; + title = function(string){ + return ''+string.replace(titleRegex, '$1'); + }; + var cmdRegex = /```([^`]*?)```/g; + cmd = function(string) { + return ''+string.replace(cmdRegex, '$1'); + }; + date = function(string){ + var D = new Date(string); + return D.toLocaleDateString()+" "+D.toLocaleTimeString(); + }; + mailRegexp = /(.*) <(.+@.+)>/; + mail = function( string ){ + return ''+string.replace(mailRegexp, `$1`); + }; + function updatePage(data){ -
-
-

${escape(item.server)}

- - ${date(escape(item.created_at))}
-
-
-

${mail(escape(item.author))}

-

- Actions -

-
- Remove - Edit -
-
-
-
-
 ${cmd(title(url(escape(item.content))))}
-
-
- `; - }); - $("#content").html(content); - -} - -$("input").on("keyup",function(e){ - const el = $(e.target); - const val = el.val(); - if( val.length < 3 ){ return; } - $.ajax("/search",{ - beforeSend: function(request) { - request.setRequestHeader("authorizationToken", authorizationToken); - }, - data: { - q:val, + var content = ""; + var item = {}; + var id = ''; + // If the log entry is unique, simulate a search result + if( ! data['hits'] ){ + data = {hits:{hits:[data]}}; } - }) - .done(function(data) { - updatePage(data); - }) - .fail(function() { - alert( "error" ); + $.each(data.hits.hits, (k,v)=>{ + + item = v._source; + id = v._id; + content += ` + +
+
+

${escape(item.server)}

+ + ${date(escape(item.created_at))}
+
+
+

${mail(escape(item.author))}

+

+ Actions +

+
+ Remove + Edit +
+
+
+
+
 ${cmd(title(url(escape(item.content))))}
+
+
+ `; + }); + $("#content").html(content); + + } + + $("input").on("keyup",function(e){ + const el = $(e.target); + const val = el.val(); + if( val.length < 3 ){ return; } + $.ajax("/search",{ + beforeSend: function(request) { + request.setRequestHeader("authorizationToken", authorizationToken); + }, + data: { + q:val, + } + }) + .done(function(data) { + updatePage(data); + }) + .fail(function() { + alert( "error" ); + }); }); + + updatePage( initData ); + + $(".actions-toggle").on("click",(e) => { var el=e.target; $(el).parent().siblings('.actions').show(); $(el).hide(); } ) + + $('.delete').on('click', (e) => { + const el = $(e.target); + const url = el.attr('href'); + $.ajax(url,{ + method: "DELETE", + beforeSend: function(request) { + request.setRequestHeader("authorizationToken", authorizationToken); + } + }) + .done(function(data) { + $(el).parents('.log').remove(); + }) + .fail(function() { + alert( "error" ); + }); + return false; + }); + }); - -updatePage( initData ); - -$(".actions-toggle").on("click",(e) => { var el=e.target; $(el).parent().siblings('.actions').show(); $(el).hide(); } ) - diff --git a/routes/index.js b/routes/index.js index 6514d2c..6ac1b64 100644 --- a/routes/index.js +++ b/routes/index.js @@ -54,6 +54,53 @@ const routes = { res.json({"health":0,"msg":"Lost connection to ES"}); }); }, + delete: (req,res) => { + const id= req.params.id; + // Reindex the doc to the "trash" index + var log = client.reindex({ + refresh: true, + max_docs: 1, + body: { + source: { + index: 'changelog', + query: { + term: { + _id: id + } + } + }, + dest: { + index: 'changelog-trash', + } + } + }) + .then( (results, err) => { + + console.log(`reindexing success for id ${id}`) + // Remove it from the original index + return client.delete({ + index: "changelog", + id: id + }); + + }, (e) => { + + console.log("reindexing error") + res.status(400); + res.end("error"); + + }) + .then( (results, err) => { + + console.log(`Delete success for id ${id}`) + res.end("ok"); + + },(results, err) => { + console.log(`Delete error for id ${id}`) + res.status(400); + res.end("error"); + }); + }, add: (req, res) => { const body = req.body; From 77ccaef87d14ba6d908b13a8a768442b2a59749c Mon Sep 17 00:00:00 2001 From: alban Date: Sun, 24 May 2020 23:02:56 +0200 Subject: [PATCH 4/4] [enh] There should be a patch method. Also, DOM events should work... --- index.js | 6 +- public/js/app.js | 169 +++++++++++++++++++++++++++++++++-------------- routes/index.js | 33 ++++++++- 3 files changed, 154 insertions(+), 54 deletions(-) diff --git a/index.js b/index.js index a63b304..99e08c9 100644 --- a/index.js +++ b/index.js @@ -38,6 +38,7 @@ app.use(express.static('public')); const bodyParser = require('body-parser'); app.use(bodyParser.json()); app.use(bodyParser.raw()); +app.use(bodyParser.urlencoded({ extended: true })); app.use(bodyParser.text({ type : "text/*" })); app.disable('x-powered-by'); @@ -62,12 +63,13 @@ function requireAuthentication( req, res, next ){ app.all('*', requireAuthentication); const routes = require( "./routes"); +app.get('/get/:id', routes.get); app.get('/log/:id', routes.log); app.get('/health', routes.health); app.get('/search', routes.search); -app.post('/*', routes.add); app.get('/*', routes.main); -app.patch('/*', routes.main); +app.post('/*', routes.add); +app.patch('/patch/:id', routes.patch); app.put('/*', routes.main); app.delete('/delete/:id', routes.delete); diff --git a/public/js/app.js b/public/js/app.js index 7838bd4..f1eec74 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -1,7 +1,100 @@ /* global initData, authorizationToken */ $(function(){ - + + const actionToggle = function(e){ + var el=e.target; + $(el).parent().siblings('.actions').show(); + $(el).hide(); + }; + + const deleteButton = function(e){ + const el = $(e.target); + const url = el.attr('href'); + $.ajax(url,{ + method: "DELETE", + beforeSend: function(request) { + request.setRequestHeader("authorizationToken", authorizationToken); + } + }) + .done(function(data) { + $(el).parents('.log').remove(); + }) + .fail(function() { + alert( "error" ); + }); + return false; + }; + + const editButton = function(e){ + const el = $(e.target); + const id = el.attr("rel"); + const pre = el.parents(".log").find("pre"); + $.ajax(`/get/${id}`,{ + beforeSend: function(request) { + request.setRequestHeader("authorizationToken", authorizationToken); + } + }) + .done(function(data) { + pre + .text(data._source.content) + .attr('rel',id) + .css("background","#ccc") + .attr("contenteditable",true) + .focus(); + $('body pre[contenteditable]').on('focusout', contentEdited ); + + }) + .fail(function(data,err) { + console.log(data,err); + alert( "error" ); + }); + + return false; + }; + + const contentEdited = function(e) { + const el = $(e.target); + const id = el.attr('rel'); + const content = el.text(); + $.ajax(`/patch/${id}`,{ + method: "PATCH", + data: {content:content}, + beforeSend: function(request) { + request.setRequestHeader("authorizationToken", authorizationToken); + } + }) + .done(function(data) { + el + .css("background","") + .html( formatContent(content) ); + }) + .fail(function(data,err) { + console.log(data,err); + alert( "error" ); + }); + }; + + const search = function(e){ + const el = $(e.target); + const val = el.val(); + if( val.length < 3 ){ return; } + $.ajax("/search",{ + beforeSend: function(request) { + request.setRequestHeader("authorizationToken", authorizationToken); + }, + data: { + q:val + } + }) + .done(function(data) { + updatePage(data); + }) + .fail(function() { + alert( "error" ); + }); + }; + // List of HTML entities for escaping. var htmlEscapes = { '&': '&', @@ -16,31 +109,37 @@ $(function(){ var htmlEscaper = /[&<>"'\/]/g; // Escape a string for HTML interpolation. - escape = function(string) { + const escape = function(string) { return ('' + string).replace(htmlEscaper, function(match) { return htmlEscapes[match]; }); }; - var urlRegex = /(\S+): (https?://[^\s]+)/g; - url = function(string){ + const urlRegex = /(\S+): (https?://[^\s]+)/g; + const url = function(string){ return ''+string.replace(urlRegex, '$1'); }; var titleRegex = /^(.*\n)/; - title = function(string){ + const title = function(string){ return ''+string.replace(titleRegex, '$1'); }; var cmdRegex = /```([^`]*?)```/g; - cmd = function(string) { + const cmd = function(string) { return ''+string.replace(cmdRegex, '$1'); }; - date = function(string){ + const date = function(string){ var D = new Date(string); return D.toLocaleDateString()+" "+D.toLocaleTimeString(); }; - mailRegexp = /(.*) <(.+@.+)>/; - mail = function( string ){ + const mailRegexp = /(.*) <(.+@.+)>/; + const mail = function( string ){ return ''+string.replace(mailRegexp, `$1`); }; + + const formatContent = function(string){ + return cmd(title(url(escape(string)))); + }; + + function updatePage(data){ var content = ""; @@ -68,61 +167,29 @@ $(function(){ Actions

-
-
 ${cmd(title(url(escape(item.content))))}
+
+
 ${formatContent(item.content)}
`; }); $("#content").html(content); + + // attache events + $(".actions-toggle").on("click",actionToggle ); + $('.delete').on('click', deleteButton); + $('.edit').on('click', editButton ); } - $("input").on("keyup",function(e){ - const el = $(e.target); - const val = el.val(); - if( val.length < 3 ){ return; } - $.ajax("/search",{ - beforeSend: function(request) { - request.setRequestHeader("authorizationToken", authorizationToken); - }, - data: { - q:val, - } - }) - .done(function(data) { - updatePage(data); - }) - .fail(function() { - alert( "error" ); - }); - }); + $("input").on("keyup",search ); updatePage( initData ); - $(".actions-toggle").on("click",(e) => { var el=e.target; $(el).parent().siblings('.actions').show(); $(el).hide(); } ) - - $('.delete').on('click', (e) => { - const el = $(e.target); - const url = el.attr('href'); - $.ajax(url,{ - method: "DELETE", - beforeSend: function(request) { - request.setRequestHeader("authorizationToken", authorizationToken); - } - }) - .done(function(data) { - $(el).parents('.log').remove(); - }) - .fail(function() { - alert( "error" ); - }); - return false; - }); }); diff --git a/routes/index.js b/routes/index.js index 6ac1b64..f30b382 100644 --- a/routes/index.js +++ b/routes/index.js @@ -116,12 +116,43 @@ const routes = { res.end("error"); }); } , + get: (req, res) => { + + const id= req.params.id; + var log = client.get({ + index: 'changelog', + id: id + }).then( (results, err) => { + res.json(results); + }, (e) => { + res.status(400); + res.json({msg:"Failed to get record"}); + }); + }, + patch: (req,res) => { + const id= req.params.id; + const content= req.body.content; + var log = client.update({ + + index: 'changelog', + id: id, + body:{ + doc:{ + content:content + } + } + }).then( (results, err) => { + res.json(results); + }, (e) => { + res.status(400); + res.json({msg:"Failed to get record"}); + }); }, log: (req, res) => { const id= req.params.id; var log = client.get({ index: 'changelog', - id: id + id: id }).then( (results, err) => { res.render('index', { title: 'changelog',