Last active
August 15, 2019 10:14
-
-
Save tabula-rasa/b1d345a770167d2bbddf40ce3fd5e05e to your computer and use it in GitHub Desktop.
An Example of exceptions logs page with quick search and rendering via mithril.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="utf-8"> | |
<meta http-equiv="X-UA-Compatible" content="IE=edge"> | |
<meta name="viewport" content="width=device-width, initial-scale=1"> | |
<link rel="stylesheet" href="main.css"> | |
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" | |
integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous"> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/mithril/2.0.3/mithril.js" | |
integrity="sha256-dxTsxBtk7R86xpX3QJkoDEaj5ml5YpD0N8VHzxzFXTE=" crossorigin="anonymous"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/Faker/3.1.0/faker.min.js" | |
integrity="sha256-QHdJObhDO++VITP6S4tMlDHRWMaUOk+s/xWIRgF/YY0=" crossorigin="anonymous"></script> | |
</head> | |
<body> | |
<script> | |
//can pass in initial values if needed | |
function ExceptionModel() { | |
var self = this; //meme | |
this.id = "ex" + faker.random.uuid(); | |
this.app = faker.name.jobArea(); | |
this.user = faker.internet.userName(); | |
this.date = faker.date.past().toLocaleString(); | |
this.showStack = false; | |
this.message = faker.lorem.sentence(); | |
this.stackID = "st" + faker.random.number(); | |
this.stackHref = faker.internet.url(); | |
this.stackEmail = faker.internet.url(); | |
this.callStack = faker.lorem.paragraphs(); | |
this.toggleStack = function () { | |
self.showStack = !self.showStack; | |
} | |
} | |
//Exception mithril component, a closure | |
function Exception(initialVnode) { | |
var state = {}; | |
return { | |
oninit: function (vnode) { | |
state = vnode.attrs.model || new ExceptionModel(); | |
}, | |
view: function (vnode) { | |
return m("div.exception.row", { | |
id: state.id, | |
onclick: state.toggleStack | |
}, [ | |
m('div.col-8', m('h2.app', m.trust(state.app))), | |
m('div.col-4.timestamp.text-right', [ | |
m('strong.mr-2', m.trust(state.user)), | |
m('span', m.trust(state.date)), | |
]), | |
m('div.col-12', [ | |
m('p.message', m.trust(state.message)), | |
(state.showStack) ? | |
m('div.callstack', { | |
id: state.stackID | |
}, [ | |
m('a.btn.btn-sm.btn-outline-primary.mr-2', { | |
href: state.stackHref | |
}, 'view'), | |
m('a.btn.btn-sm.btn-outline-secondary', { | |
href: state.stackEmail | |
}, 'email'), | |
m('p', m.trust(state.callStack)), | |
]) : null, | |
]) | |
]) | |
} | |
} | |
} | |
//shared navbar state | |
function NavbarModel() { | |
var self = this; //meme | |
this.query = ""; | |
this.setQuery = function (val) { | |
self.query = val; | |
} | |
} | |
//Navbar mithril component, a closure | |
function Navbar(initialVnode) { | |
var state = {}; | |
return { | |
oninit: function (vnode) { | |
state = vnode.attrs.model || new NavbarModel(); | |
}, | |
view: function (vnode) { | |
return m('nav.navbar.navbar-light.bg-light.fixed-top', [ | |
m('div.form-inline', [ | |
m('input.form-control#query[type=text][placeholder=Search...]', { | |
oninput: function (e) { | |
state.setQuery(e.target.value); | |
if (vnode.attrs.onQueryChange) | |
vnode.attrs.onQueryChange(e.target.value); | |
}, | |
value: state.query | |
}) | |
]), | |
]) | |
} | |
} | |
} | |
//Main application mithril component, a closure | |
function App(initialVnode) { | |
var totalExceptions = 1000; //total number of generated logs | |
var exceptions = new Array(totalExceptions).fill(null) | |
var navbarState = new NavbarModel(); | |
var clearRe = new RegExp("(<mark>)([^<>]*)(<\\/mark>)", "g"); | |
var markRe = new RegExp("(" + navbarState.query + ")", "gi"); | |
var queryRe = new RegExp(navbarState.query, 'i'); | |
//rebuild regexes that depend on search query change | |
var onQueryChange = function (val) { | |
markRe = new RegExp("(" + val + ")", "gi"); | |
queryRe = new RegExp(val, 'i'); | |
} | |
//there is room for optimizations | |
//filter exceptions by query string | |
var filterException = function (ex) { | |
if (navbarState.query.trim().length == 0) | |
return true; | |
return queryRe.test(ex.app) || queryRe.test(ex.user) || queryRe.test(ex.message) || | |
queryRe.test(ex.callStack) || queryRe.test(ex.date) | |
} | |
//clear previously highlighted occurences | |
var clearHighlights = function (ex) { | |
//clear old marks | |
ex.app = ex.app.replace(clearRe, "$2"); | |
ex.user = ex.user.replace(clearRe, "$2"); | |
ex.message = ex.message.replace(clearRe, "$2"); | |
ex.callStack = ex.callStack.replace(clearRe, "$2"); | |
ex.date = ex.date.replace(clearRe, "$2"); | |
return ex; | |
} | |
//highlight occurences | |
var highlightParts = function (ex) { | |
if (navbarState.query.trim().length < 1) | |
return ex; | |
ex.app = ex.app.replace(markRe, "<mark>$1</mark>"); | |
ex.user = ex.user.replace(markRe, "<mark>$1</mark>"); | |
ex.message = ex.message.replace(markRe, "<mark>$1</mark>"); | |
ex.callStack = ex.callStack.replace(markRe, "<mark>$1</mark>"); | |
ex.date = ex.date.replace(markRe, "<mark>$1</mark>"); | |
return ex; | |
} | |
return { | |
oninit: function (vnode) { | |
exceptions = exceptions.map(function () { | |
return new ExceptionModel() | |
}); | |
}, | |
view: function (vnode) { | |
return [ | |
m(Navbar, { | |
model: navbarState, | |
onQueryChange: onQueryChange, | |
}), | |
m('div.container-fluid', | |
//highlights sometimes throw exception "vnode3.instance is undefined" on collection rebuild, but are still working, need to look into it later | |
//exceptions.map(clearHighlights).filter(filterException).map(highlightParts).map(function (e) { | |
exceptions.filter(filterException).map(function (e) { | |
return m(Exception, { | |
key: e.id, //important for filtering | |
model: e | |
}); | |
}), | |
), | |
] | |
} | |
} | |
} | |
//mount our app on page body | |
m.mount(document.body, App) | |
</script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
body { | |
font-family: Arial, Helvetica, sans-serif; | |
font-size: medium; | |
margin: 8px; | |
cursor: pointer; | |
} | |
div.cell { | |
position: relative; | |
width: 100%; | |
border-top: 1px solid darkgray; | |
} | |
.even { | |
background: #ccf | |
} | |
div.cell:hover { | |
background: #aaf | |
} | |
h2.app { | |
font-size: 110%; | |
color: darkslategrey; | |
padding-left: 4px; | |
font-family: Verdana; | |
font-weight: bold; | |
margin-top: 8px; | |
margin-bottom: 4px; | |
} | |
.timestamp { | |
font-style: italic; | |
color: #333; | |
font-size: 70%; | |
padding-top: 7px; | |
} | |
.timestamp .user { | |
font-style: normal; | |
font-weight: bold; | |
} | |
.message { | |
padding: 2px 2px 0 15px; | |
margin-bottom: 0; | |
font-size: medium; | |
font-family: 'Times New Roman'; | |
} | |
.callstack { | |
padding: 2px 15px 6px 15px; | |
border-left: 5px solid olive; | |
font-size: small; | |
} | |
.hidden { | |
display: none !important; | |
} | |
.exception { | |
position: relative; | |
padding: 10px 5px 17px; | |
} | |
.row:nth-child(even) { | |
background: #CEC | |
} | |
.row:hover:nth-child(even) { | |
background: #BEB | |
} | |
.row:hover:nth-child(odd) { | |
background: #BEB | |
} | |
.row:nth-child(odd) { | |
background: #FFF | |
} | |
.navbar { | |
box-shadow: 0 1px 5px rgba(0, 0, 0, 0.3); | |
} | |
.navbar .form-inline { | |
margin: 0; | |
} | |
.navbar.fixed-top+* { | |
margin-top: 70px; | |
margin-bottom: 50px; | |
} | |
#query { | |
width: 400px; | |
transition: width 0.3s ease; | |
} | |
#query:focus { | |
width: 100%; | |
} | |
.form-inline { | |
width: 100%; | |
} | |
mark { | |
padding: 0.2em 0 !important; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment