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/index.js b/index.js index b453b21..99e08c9 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(); @@ -40,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'); @@ -64,14 +63,15 @@ 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('/*', routes.main); +app.delete('/delete/:id', routes.delete); app.listen(port, () => { diff --git a/public/css/site.css b/public/css/site.css index c4f874c..10f4a7c 100644 --- a/public/css/site.css +++ b/public/css/site.css @@ -5,45 +5,49 @@ */ .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; + text-align: right; + } + .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: #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..f1eec74 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -1,103 +1,195 @@ /* global initData, authorizationToken */ -// List of HTML entities for escaping. -var htmlEscapes = { - '&': '&', - '<': '<', - '>': '>', - '"': '"', - "'": ''', - '/': '/' -}; +$(function(){ -// Regex containing the keys listed immediately above. -var htmlEscaper = /[&<>"'\/]/g; + const actionToggle = function(e){ + var el=e.target; + $(el).parent().siblings('.actions').show(); + $(el).hide(); + }; -// 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(date){ - var D = new Date(date); - return D.toLocaleDateString()+" "+D.toLocaleTimeString(); -} -function updatePage(data){ + 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" ); + }); + }; - 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 += ` + 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" ); + }); + }; -
-
-

${escape(item.server)}

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

${escape(item.author)}

-

- Actions -

-
- Remove - Edit -
-
-
-
-
 ${cmd(title(url(escape(item.content))))}
-
-
- `; - }); - $("#content").html(content); + // List of HTML entities for escaping. + var htmlEscapes = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + '/': '/' + }; + + // Regex containing the keys listed immediately above. + var htmlEscaper = /[&<>"'\/]/g; + + // Escape a string for HTML interpolation. + const escape = function(string) { + return ('' + string).replace(htmlEscaper, function(match) { + return htmlEscapes[match]; + }); + }; + const urlRegex = /(\S+): (https?://[^\s]+)/g; + const url = function(string){ + return ''+string.replace(urlRegex, '$1'); + }; + var titleRegex = /^(.*\n)/; + const title = function(string){ + return ''+string.replace(titleRegex, '$1'); + }; + var cmdRegex = /```([^`]*?)```/g; + const cmd = function(string) { + return ''+string.replace(cmdRegex, '$1'); + }; + const date = function(string){ + var D = new Date(string); + return D.toLocaleDateString()+" "+D.toLocaleTimeString(); + }; + const mailRegexp = /(.*) <(.+@.+)>/; + const mail = function( string ){ + return ''+string.replace(mailRegexp, `$1`); + }; -} + const formatContent = function(string){ + return cmd(title(url(escape(string)))); + }; + + + function updatePage(data){ -$("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 +
+
+
+
+
 ${formatContent(item.content)}
+
+
+ `; + }); + $("#content").html(content); + + // attache events + $(".actions-toggle").on("click",actionToggle ); + $('.delete').on('click', deleteButton); + $('.edit').on('click', editButton ); + + } + + $("input").on("keyup",search ); + + updatePage( initData ); + + }); - -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..f30b382 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; @@ -69,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',