Skip to content

Instantly share code, notes, and snippets.

@tabula-rasa
Last active August 15, 2019 10:14
Show Gist options
  • Save tabula-rasa/b1d345a770167d2bbddf40ce3fd5e05e to your computer and use it in GitHub Desktop.
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
<!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>
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