Compare commits
4 Commits
56ce8cb645
...
77ccaef87d
Author | SHA1 | Date | |
---|---|---|---|
|
77ccaef87d | ||
|
b0c6043741 | ||
|
3ce2e4514d | ||
|
5bedb0c2a9 |
@ -6,17 +6,17 @@ const dbInit = {};
|
|||||||
|
|
||||||
const bulkData = [
|
const bulkData = [
|
||||||
{ "index" : { "_index" : "changelog" } },
|
{ "index" : { "_index" : "changelog" } },
|
||||||
{ "author" : "John Ripper <john@theripper.com",
|
{ "author" : "John Ripper <john@theripper.com>",
|
||||||
"content":"* machines: Installed the server\n```debootstrap -t foobar```",
|
"content":"* machines: Installed the server\n```debootstrap -t foobar```",
|
||||||
"server": "server.example.com",
|
"server": "server.example.com",
|
||||||
"created_at":"2020-05-23T09:50:33.397Z"},
|
"created_at":"2020-05-23T09:50:33.397Z"},
|
||||||
{ "index" : { "_index" : "changelog" } },
|
{ "index" : { "_index" : "changelog" } },
|
||||||
{ "author" : "John Ripper <john@theripper.com",
|
{ "author" : "John Ripper <john@theripper.com>",
|
||||||
"content":"* db: Installed mysql\n```apt install mariadb-server```",
|
"content":"* db: Installed mysql\n```apt install mariadb-server```",
|
||||||
"server": "server.example.com",
|
"server": "server.example.com",
|
||||||
"created_at":"2020-05-23T10:50:33.397Z"},
|
"created_at":"2020-05-23T10:50:33.397Z"},
|
||||||
{ "index" : { "_index" : "changelog" } },
|
{ "index" : { "_index" : "changelog" } },
|
||||||
{ "author" : "John Ripper <john@theripper.com",
|
{ "author" : "John Ripper <john@theripper.com>",
|
||||||
"content":"* nginx: add package\n```apt install nginx-full```",
|
"content":"* nginx: add package\n```apt install nginx-full```",
|
||||||
"server": "server.example.com",
|
"server": "server.example.com",
|
||||||
"created_at":"2020-05-23T16:50:33.397Z"}
|
"created_at":"2020-05-23T16:50:33.397Z"}
|
||||||
|
10
index.js
10
index.js
@ -29,8 +29,6 @@ dbInit.init({
|
|||||||
seed : process.env.DB_SEED
|
seed : process.env.DB_SEED
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log( "exit")
|
|
||||||
|
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
@ -40,6 +38,7 @@ app.use(express.static('public'));
|
|||||||
const bodyParser = require('body-parser');
|
const bodyParser = require('body-parser');
|
||||||
app.use(bodyParser.json());
|
app.use(bodyParser.json());
|
||||||
app.use(bodyParser.raw());
|
app.use(bodyParser.raw());
|
||||||
|
app.use(bodyParser.urlencoded({ extended: true }));
|
||||||
app.use(bodyParser.text({ type : "text/*" }));
|
app.use(bodyParser.text({ type : "text/*" }));
|
||||||
app.disable('x-powered-by');
|
app.disable('x-powered-by');
|
||||||
|
|
||||||
@ -64,14 +63,15 @@ function requireAuthentication( req, res, next ){
|
|||||||
app.all('*', requireAuthentication);
|
app.all('*', requireAuthentication);
|
||||||
|
|
||||||
const routes = require( "./routes");
|
const routes = require( "./routes");
|
||||||
|
app.get('/get/:id', routes.get);
|
||||||
app.get('/log/:id', routes.log);
|
app.get('/log/:id', routes.log);
|
||||||
app.get('/health', routes.health);
|
app.get('/health', routes.health);
|
||||||
app.get('/search', routes.search);
|
app.get('/search', routes.search);
|
||||||
app.post('/*', routes.add);
|
|
||||||
app.get('/*', routes.main);
|
app.get('/*', routes.main);
|
||||||
app.patch('/*', routes.main);
|
app.post('/*', routes.add);
|
||||||
|
app.patch('/patch/:id', routes.patch);
|
||||||
app.put('/*', routes.main);
|
app.put('/*', routes.main);
|
||||||
app.delete('/*', routes.main);
|
app.delete('/delete/:id', routes.delete);
|
||||||
|
|
||||||
|
|
||||||
app.listen(port, () => {
|
app.listen(port, () => {
|
||||||
|
@ -5,45 +5,49 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
.log {
|
.log {
|
||||||
margin-bottom: 10px;
|
padding: 16px 0;
|
||||||
border-top: 1px solid #eee;
|
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.log .meta a {
|
.log .meta {
|
||||||
color: #666;
|
|
||||||
font-size: .875rem;
|
font-size: .875rem;
|
||||||
|
color: #888;
|
||||||
|
line-height:1.2em;
|
||||||
|
text-align: right;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.log .meta p {
|
.log .meta p {
|
||||||
font-size: .875rem;
|
margin: 0px;
|
||||||
line-height:1em;
|
overflow: hidden;
|
||||||
margin: 0px;
|
text-overflow: ellipsis;
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
}
|
||||||
.log .meta p.server {
|
.log .meta p.server {
|
||||||
color:#333;
|
color: #333;
|
||||||
|
font-size:1.125rem;
|
||||||
}
|
}
|
||||||
.log .meta .actions-toggle {
|
.log .meta .actions-toggle {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
color: #666;
|
cursor: pointer;
|
||||||
cursor: pointer;
|
line-height:1em;
|
||||||
line-height:1em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.log .meta .actions{
|
.log .meta .actions{
|
||||||
display:none;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
278
public/js/app.js
278
public/js/app.js
@ -1,103 +1,195 @@
|
|||||||
/* global initData, authorizationToken */
|
/* global initData, authorizationToken */
|
||||||
|
|
||||||
// List of HTML entities for escaping.
|
$(function(){
|
||||||
var htmlEscapes = {
|
|
||||||
'&': '&',
|
|
||||||
'<': '<',
|
|
||||||
'>': '>',
|
|
||||||
'"': '"',
|
|
||||||
"'": ''',
|
|
||||||
'/': '/'
|
|
||||||
};
|
|
||||||
|
|
||||||
// Regex containing the keys listed immediately above.
|
const actionToggle = function(e){
|
||||||
var htmlEscaper = /[&<>"'\/]/g;
|
var el=e.target;
|
||||||
|
$(el).parent().siblings('.actions').show();
|
||||||
|
$(el).hide();
|
||||||
|
};
|
||||||
|
|
||||||
// Escape a string for HTML interpolation.
|
const deleteButton = function(e){
|
||||||
escape = function(string) {
|
const el = $(e.target);
|
||||||
return ('' + string).replace(htmlEscaper, function(match) {
|
const url = el.attr('href');
|
||||||
return htmlEscapes[match];
|
$.ajax(url,{
|
||||||
});
|
method: "DELETE",
|
||||||
};
|
beforeSend: function(request) {
|
||||||
var urlRegex = /(\S+): (https?://[^\s]+)/g;
|
request.setRequestHeader("authorizationToken", authorizationToken);
|
||||||
url = function(string){
|
}
|
||||||
return ''+string.replace(urlRegex, '<a target="_blank" href="$2">$1</a>')
|
})
|
||||||
}
|
.done(function(data) {
|
||||||
var titleRegex = /^(.*\n)/;
|
$(el).parents('.log').remove();
|
||||||
title = function(string){
|
})
|
||||||
return ''+string.replace(titleRegex, '<b>$1</b>');
|
.fail(function() {
|
||||||
}
|
alert( "error" );
|
||||||
var cmdRegex = /```([^`]*?)```/g
|
});
|
||||||
cmd = function(string) {
|
return false;
|
||||||
return ''+string.replace(cmdRegex, '<span class="cmd">$1</span>');
|
};
|
||||||
}
|
|
||||||
date = function(date){
|
const editButton = function(e){
|
||||||
var D = new Date(date);
|
const el = $(e.target);
|
||||||
return D.toLocaleDateString()+" "+D.toLocaleTimeString();
|
const id = el.attr("rel");
|
||||||
}
|
const pre = el.parents(".log").find("pre");
|
||||||
function updatePage(data){
|
$.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 = "";
|
const search = function(e){
|
||||||
var item = {};
|
const el = $(e.target);
|
||||||
var id = '';
|
const val = el.val();
|
||||||
// If the log entry is unique, simulate a search result
|
if( val.length < 3 ){ return; }
|
||||||
if( ! data['hits'] ){
|
$.ajax("/search",{
|
||||||
data = {hits:{hits:[data]}};
|
beforeSend: function(request) {
|
||||||
}
|
request.setRequestHeader("authorizationToken", authorizationToken);
|
||||||
$.each(data.hits.hits, (k,v)=>{
|
},
|
||||||
|
data: {
|
||||||
item = v._source;
|
q:val
|
||||||
id = v._id;
|
}
|
||||||
content += `
|
})
|
||||||
|
.done(function(data) {
|
||||||
|
updatePage(data);
|
||||||
|
})
|
||||||
|
.fail(function() {
|
||||||
|
alert( "error" );
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
<div class="log row">
|
// List of HTML entities for escaping.
|
||||||
<div class="meta col-lg-2 text-right">
|
var htmlEscapes = {
|
||||||
<p class="server"> ${escape(item.server)} </p>
|
'&': '&',
|
||||||
<a href="/log/${id}">
|
'<': '<',
|
||||||
${date(escape(item.created_at))} <br/>
|
'>': '>',
|
||||||
</a>
|
'"': '"',
|
||||||
<div class="d-none d-lg-block">
|
"'": ''',
|
||||||
<p class="author"> ${escape(item.author)} </p>
|
'/': '/'
|
||||||
<p>
|
};
|
||||||
<a class="actions-toggle btn-link btn-sm">Actions</a>
|
|
||||||
</p>
|
// Regex containing the keys listed immediately above.
|
||||||
<div class="actions btn-group btn-group-sm" role="group" aria-label="log actions">
|
var htmlEscaper = /[&<>"'\/]/g;
|
||||||
<a class="destroy btn btn btn-outline-secondary" href="/destroy/${id}">Remove</a>
|
|
||||||
<a class="edit btn btn btn-outline-secondary" href="/edit/${id}">Edit</a>
|
// Escape a string for HTML interpolation.
|
||||||
</div>
|
const escape = function(string) {
|
||||||
</div>
|
return ('' + string).replace(htmlEscaper, function(match) {
|
||||||
</div>
|
return htmlEscapes[match];
|
||||||
<div class="col-lg">
|
});
|
||||||
<pre> ${cmd(title(url(escape(item.content))))}</pre>
|
};
|
||||||
</div>
|
const urlRegex = /(\S+): (https?://[^\s]+)/g;
|
||||||
</div>
|
const url = function(string){
|
||||||
`;
|
return ''+string.replace(urlRegex, '<a target="_blank" href="$2">$1</a>');
|
||||||
});
|
};
|
||||||
$("#content").html(content);
|
var titleRegex = /^(.*\n)/;
|
||||||
|
const title = function(string){
|
||||||
|
return ''+string.replace(titleRegex, '<b>$1</b>');
|
||||||
|
};
|
||||||
|
var cmdRegex = /```([^`]*?)```/g;
|
||||||
|
const cmd = function(string) {
|
||||||
|
return ''+string.replace(cmdRegex, '<span class="cmd">$1</span>');
|
||||||
|
};
|
||||||
|
const date = function(string){
|
||||||
|
var D = new Date(string);
|
||||||
|
return D.toLocaleDateString()+" "+D.toLocaleTimeString();
|
||||||
|
};
|
||||||
|
const mailRegexp = /(.*) <(.+@.+)>/;
|
||||||
|
const mail = function( string ){
|
||||||
|
return ''+string.replace(mailRegexp, `<a href="mailto:${string}">$1</a>`);
|
||||||
|
};
|
||||||
|
|
||||||
}
|
const formatContent = function(string){
|
||||||
|
return cmd(title(url(escape(string))));
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
function updatePage(data){
|
||||||
|
|
||||||
$("input").on("keyup",function(e){
|
var content = "";
|
||||||
const el = $(e.target);
|
var item = {};
|
||||||
const val = el.val();
|
var id = '';
|
||||||
if( val.length < 3 ){ return; }
|
// If the log entry is unique, simulate a search result
|
||||||
$.ajax("/search",{
|
if( ! data['hits'] ){
|
||||||
beforeSend: function(request) {
|
data = {hits:{hits:[data]}};
|
||||||
request.setRequestHeader("authorizationToken", authorizationToken);
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
q:val,
|
|
||||||
}
|
}
|
||||||
})
|
$.each(data.hits.hits, (k,v)=>{
|
||||||
.done(function(data) {
|
|
||||||
updatePage(data);
|
item = v._source;
|
||||||
})
|
id = v._id;
|
||||||
.fail(function() {
|
content += `
|
||||||
alert( "error" );
|
|
||||||
});
|
<div class="log row">
|
||||||
|
<div class="meta col-lg-2 ">
|
||||||
|
<p class="server"> ${escape(item.server)} </p>
|
||||||
|
<a href="/log/${id}">
|
||||||
|
${date(escape(item.created_at))} <br/>
|
||||||
|
</a>
|
||||||
|
<div class="d-none d-lg-block">
|
||||||
|
<p class="author"> ${mail(escape(item.author))} </p>
|
||||||
|
<p>
|
||||||
|
<a class="actions-toggle btn-link btn-sm">Actions</a>
|
||||||
|
</p>
|
||||||
|
<div class="actions btn-group btn-group-sm" role="group" aria-label="log actions">
|
||||||
|
<a class="delete btn btn btn-outline-secondary" rel="${id}" href="/delete/${id}">Remove</a>
|
||||||
|
<a class="edit btn btn btn-outline-secondary" rel="${id}" href="/edit/${id}">Edit</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg data">
|
||||||
|
<pre> ${formatContent(item.content)}</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
$("#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(); } )
|
|
||||||
|
|
||||||
|
@ -54,6 +54,53 @@ const routes = {
|
|||||||
res.json({"health":0,"msg":"Lost connection to ES"});
|
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) => {
|
add: (req, res) => {
|
||||||
|
|
||||||
const body = req.body;
|
const body = req.body;
|
||||||
@ -69,12 +116,43 @@ const routes = {
|
|||||||
res.end("error");
|
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) => {
|
log: (req, res) => {
|
||||||
|
|
||||||
const id= req.params.id;
|
const id= req.params.id;
|
||||||
var log = client.get({
|
var log = client.get({
|
||||||
index: 'changelog',
|
index: 'changelog',
|
||||||
id: id
|
id: id
|
||||||
}).then( (results, err) => {
|
}).then( (results, err) => {
|
||||||
res.render('index', {
|
res.render('index', {
|
||||||
title: 'changelog',
|
title: 'changelog',
|
||||||
|
Loading…
Reference in New Issue
Block a user