Skip to content

Commit

Permalink
Add ability to view log detail
Browse files Browse the repository at this point in the history
Includes refacotring of panel and layout components.
  • Loading branch information
iaincollins committed Nov 25, 2021
1 parent d5cb5c7 commit 9efce62
Show file tree
Hide file tree
Showing 16 changed files with 216 additions and 68 deletions.
12 changes: 12 additions & 0 deletions src/web/components/layout/main-layout.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@

export default function MainLayout ({ children, visible }) {
return (
<div className='layout__main' style={{ opacity: visible ? 1 : 0 }}>
{children}
</div>
)
}

MainLayout.defaultProps = {
visible: true
}
8 changes: 8 additions & 0 deletions src/web/components/layout/panel-layout.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@

export default function PanelLayout ({ children, layout = 'full-width' }) {
return (
<div className={`layout__${layout} scrollable`}>
{children}
</div>
)
}
33 changes: 33 additions & 0 deletions src/web/components/layout/toolbar.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { useState, useEffect } from 'react'
import { toggleFullScreen } from 'lib/window'
import { eliteDateTime } from 'lib/format'

export default function Toolbar ({ connected }) {
const [dateTime, setDateTime] = useState(0)

useEffect(() => {
eliteDateTime()
const timeout = setTimeout(() => {
setDateTime(eliteDateTime())
}, 1000)

return () => clearTimeout(timeout)
}, [])

return (
<>
<hr className='small' />
<h2 style={{ padding: '.5rem 0' }}> ICARUS Terminal</h2>
<div style={{ position: 'absolute', top: '1.6rem', right: '1rem' }}>
<h3 className='text-primary' style={{ display: 'inline', position: 'relative', top: '-.5rem', left: '-.5rem' }}>{dateTime}</h3>
<button disabled className='button-with-icon button-transparent' style={{ opacity: 1, marginRight: '.5rem' }}>
<i className={`icarus-terminal-signal ${connected ? 'text-primary' : 'text-danger text-blink'}`} />
</button>
<button onClick={toggleFullScreen} className='button-with-icon'>
<i className='icarus-terminal-fullscreen' />
</button>
</div>
<hr className='bold' />
</>
)
}
2 changes: 1 addition & 1 deletion src/web/components/loader.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

export default function Loader ({ visible }) {
return (
<div id='loader' style={{ opacity: visible ? .75 : 0 }}>
<div id='loader' style={{ opacity: visible ? 0.75 : 0 }}>
<div className='loader__row'>
<div className='loader__arrow loader__arrow--outer-18' />
<div className='loader__arrow loader__arrow--down lloader__arrow--outer-17' />
Expand Down
12 changes: 0 additions & 12 deletions src/web/components/panel.js

This file was deleted.

14 changes: 14 additions & 0 deletions src/web/components/panels/log-entry-panel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { objectToHtml } from 'lib/format'

export default function LogPanel ({ logEntry }) {
return (
<div style={{ paddingLeft: '1rem' }}>
<h2 className='text-primary'>Log Entry</h2>
<div
className='selectable' dangerouslySetInnerHTML={{
__html: `${objectToHtml(logEntry)}`
}}
/>
</div>
)
}
9 changes: 4 additions & 5 deletions src/web/components/panels/log-panel.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
import { eliteDateTime } from 'lib/format'

export default function LogPanel (props) {
const { logEntries } = props
export default function LogPanel ({ logEntries, setSelectedLogEntry }) {
return (
<table>
<thead>
<tr>
<th>Name</th>
<th className='text-right'>Timestamp</th>
<th>Event</th>
<th className='text-right'>Time</th>
</tr>
</thead>
<tbody>
{logEntries && logEntries.map(logEntry =>
<tr key={`${logEntry._checksum}`}>
<tr key={`${logEntry._checksum}`} tabIndex='2' onFocus={() => setSelectedLogEntry(logEntry)}>
<td>{logEntry.event}</td>
<td className='text-right'>{eliteDateTime(logEntry.timestamp)}</td>
</tr>
Expand Down
17 changes: 0 additions & 17 deletions src/web/components/toolbar.js

This file was deleted.

43 changes: 43 additions & 0 deletions src/web/css/layout.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@

.layout__background {
position: absolute !important;
top: 0;
left: 0;
width: 100%;
height: 100%;
content: ' ';
background: radial-gradient(circle, var(--background-color) 0%, rgba(0,0,0,1) 100%);
z-index: -1;
opacity: 0.8;
}

.layout__main {
position: absolute;
top: 6rem;
bottom: 1rem;
left: 1rem;
right: 1rem;
transition: .25s ease-in-out;
opacity: 0;
overflow: hidden;
z-index: 10;
}

.laytout__full-width {
width: 100%;
height: 100%;
}

.layout__left-half,
.layout__right-half {
width: 50%;
height: 100%;
}

.layout__left-half {
float: left;
}

.layout__right-half {
float: right;
}
31 changes: 19 additions & 12 deletions src/web/css/main.css
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
@import "variables.css";

@import "layout.css";
@import "panels.css";
@import "loader.css";
@import "overlay.css";
@import "buttons.css";
@import "text.css";
@import "panels.css";
@import "table.css";

@import "icarus-terminal-font.css";
Expand Down Expand Up @@ -169,18 +170,24 @@ progress[value]::-webkit-progress-value {
transition : width 1s ease;
}

#background {
position: absolute !important;
top: 0;
left: 0;
width: 100%;
height: 100%;
content: ' ';
background: radial-gradient(circle, var(--background-color) 0%, rgba(0,0,0,1) 100%);
z-index: -1;
hr {
border: none;
height: .15rem;
background: var(--primary-color);
margin: .5rem 0;
box-shadow: 0 .03rem .5rem var(--primary-color), 0 0 .7rem -.03rem var(--primary-color) !important;
opacity: 0.8;
}

#main {
z-index: 10;
hr.small {
height: .05rem;
}

hr.large {
height: 2.5rem;
}

hr.bold {
box-shadow: 0 .03rem .5rem var(--primary-color), 0 0rem .7rem -.03rem var(--primary-color), 0 0rem 1rem -.01rem var(--dark-primary-color) !important;
opacity: 1;
}
4 changes: 0 additions & 4 deletions src/web/css/panels.css
Original file line number Diff line number Diff line change
@@ -1,4 +0,0 @@
.panel {
transition: .25s ease-in-out;
opacity: 0;
}
18 changes: 18 additions & 0 deletions src/web/css/text.css
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,22 @@

@keyframes text-blink-animation {
50% { opacity: 0; }
}


.text-formatted-object {
color: var(--primary-text-color);
}

.text-formatted-object-property {
margin: .5rem 0;
}

.text-formatted-object label {
display: inline-block;
text-transform: capitalize;
}

.text-formatted-object .text-formatted-object-value {
color: var(--secondary-text-color) !important;
}
42 changes: 40 additions & 2 deletions src/web/lib/format.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ function formatBytes (bytes) {
return bytes
}

function eliteDateTime (timestamp) {
function eliteDateTime (timestamp = Date.now()) {
const date = new Date(timestamp)
date.setFullYear(date.getFullYear() + 1286) // We are living in the future
return date.toUTCString()
Expand All @@ -25,7 +25,45 @@ function eliteDateTime (timestamp) {
.replace(/^0/, '') // Strip leading zeros from day of month
}

function objectToHtml (obj, depth = 0, type = null) {
const tag = 'div'
let str = ''

if (depth === 0) str = `<${tag} class="text-formatted-object">`

for (const propertyName in obj) {
if (propertyName.startsWith('_')) continue // Skip internal properties
str += `<div class="text-formatted-object-property" data-depth="${depth}" style="margin-left: ${depth}rem">`
const propertyLabel = `<label>${(type === 'array') ? 'Item ' : ''}${propertyName.replace(/([a-z])([A-Z])/g, '$1 $2').replaceAll('_', ' ').trim()}${(type === 'array') ? ':' : ''}</label>`
switch (typeof obj[propertyName]) {
case 'string':
case 'number':
case 'boolean':
str += propertyLabel + ' <span class="text-formatted-object-value">' + obj[propertyName] + '</span>'
break
case 'object':
default:
if (Array.isArray(obj[propertyName])) {
if (obj[propertyName].length > 0) {
str += propertyLabel + objectToHtml(obj[propertyName], depth + 1, 'array')
} else {
str += propertyLabel + ' <span class="text-formatted-object-value">NONE</span>'
}
} else {
str += propertyLabel + objectToHtml(obj[propertyName], depth + 1)
}
break
}
str += '</div>'
}

if (depth === 0) str += `</${tag}>`

return str
}

module.exports = {
formatBytes,
eliteDateTime
eliteDateTime,
objectToHtml
}
8 changes: 3 additions & 5 deletions src/web/pages/_document.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,9 @@ class MyDocument extends Document {
}}
/>
<body className='not-selectable'>
<div id='background' />
<div id='main'>
<Main />
<NextScript />
</div>
<div className='layout__background' />
<Main />
<NextScript />
</body>
</Html>
)
Expand Down
26 changes: 19 additions & 7 deletions src/web/pages/index.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,32 @@
import { useState, useEffect } from 'react'
import Toolbar from 'components/toolbar'
import Toolbar from 'components/layout/toolbar'
import Loader from 'components/loader'
import Panel from 'components/panel'
import MainLayout from 'components/layout/main-layout'
import PanelLayout from 'components/layout/panel-layout'
import LogPanel from 'components/panels/log-panel'
import LogEntryPanel from 'components/panels/log-entry-panel'
import { useSocket, useEventListener } from 'lib/socket'

let loadNewLogEntries

export default function IndexPage () {
const { connected, sendEvent } = useSocket()
const [logEntries, setLogEntries] = useState([])
const [selectedLogEntry, setSelectedLogEntry] = useState()

useEffect(async () => {
const newLogEntries = await sendEvent('getLogEntries')
if (Array.isArray(newLogEntries) && newLogEntries.length > 0) {
setLogEntries(newLogEntries)
// Only select a log entry if one isn't selected already
setSelectedLogEntry(prevState => prevState || newLogEntries[0])
}
}, [connected])

useEffect(() => useEventListener('newLogEntry', async (newLogEntry) => {
setLogEntries(prevState => [newLogEntry, ...prevState])
// Only select a log entry if one isn't selected already
setSelectedLogEntry(prevState => prevState || newLogEntry)
}), [])

useEffect(() => useEventListener('loadingProgress', async (message) => {
Expand All @@ -30,6 +37,8 @@ export default function IndexPage () {
const newLogEntries = await sendEvent('getLogEntries')
if (Array.isArray(newLogEntries) && newLogEntries.length > 0) {
setLogEntries(newLogEntries)
// Only select a log entry if one isn't selected already
setSelectedLogEntry(prevState => prevState || newLogEntries[0])
}
loadNewLogEntries = null
}, 1000)
Expand All @@ -40,11 +49,14 @@ export default function IndexPage () {
<>
<Toolbar connected={connected} />
<Loader visible={!connected || logEntries.length === 0} />
<Panel visible={connected && logEntries.length > 0}>
<div className='scrollable' style={{ position: 'absolute', top: '5rem', bottom: '1rem', left: '1rem', right: '1rem' }}>
<LogPanel logEntries={logEntries} />
</div>
</Panel>
<MainLayout visible={connected && logEntries.length > 0}>
<PanelLayout layout='left-half'>
<LogPanel logEntries={logEntries} setSelectedLogEntry={setSelectedLogEntry} />
</PanelLayout>
<PanelLayout layout='right-half'>
<LogEntryPanel logEntry={selectedLogEntry} />
</PanelLayout>
</MainLayout>
</>
)
}
Loading

0 comments on commit 9efce62

Please sign in to comment.