Compare commits
2 Commits
9649666195
...
271ff151a1
| Author | SHA1 | Date | |
|---|---|---|---|
| 271ff151a1 | |||
| 3eef4c2a0e |
|
|
@ -86,9 +86,9 @@ function parseTime(s: string): { hour: number, minute: number } | null {
|
||||||
// 12h: "7am", "7pm", "7:30am", "7:30pm", "12am", "12:00pm"
|
// 12h: "7am", "7pm", "7:30am", "7:30pm", "12am", "12:00pm"
|
||||||
const m12 = s.match(/^(\d{1,2})(?::(\d{2}))?\s*(am|pm)$/i)
|
const m12 = s.match(/^(\d{1,2})(?::(\d{2}))?\s*(am|pm)$/i)
|
||||||
if (m12) {
|
if (m12) {
|
||||||
let hour = parseInt(m12[1])
|
let hour = parseInt(m12[1]!)
|
||||||
const minute = m12[2] ? parseInt(m12[2]) : 0
|
const minute = m12[2] ? parseInt(m12[2]) : 0
|
||||||
const period = m12[3].toLowerCase()
|
const period = m12[3]!.toLowerCase()
|
||||||
if (hour < 1 || hour > 12 || minute > 59) return null
|
if (hour < 1 || hour > 12 || minute > 59) return null
|
||||||
if (period === 'am' && hour === 12) hour = 0
|
if (period === 'am' && hour === 12) hour = 0
|
||||||
else if (period === 'pm' && hour !== 12) hour += 12
|
else if (period === 'pm' && hour !== 12) hour += 12
|
||||||
|
|
@ -98,8 +98,8 @@ function parseTime(s: string): { hour: number, minute: number } | null {
|
||||||
// 24h: "14:00", "0:00", "23:59"
|
// 24h: "14:00", "0:00", "23:59"
|
||||||
const m24 = s.match(/^(\d{1,2}):(\d{2})$/)
|
const m24 = s.match(/^(\d{1,2}):(\d{2})$/)
|
||||||
if (m24) {
|
if (m24) {
|
||||||
const hour = parseInt(m24[1])
|
const hour = parseInt(m24[1]!)
|
||||||
const minute = parseInt(m24[2])
|
const minute = parseInt(m24[2]!)
|
||||||
if (hour > 23 || minute > 59) return null
|
if (hour > 23 || minute > 59) return null
|
||||||
return { hour, minute }
|
return { hour, minute }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@ import {
|
||||||
LogsBody,
|
LogsBody,
|
||||||
LogsHeader,
|
LogsHeader,
|
||||||
LogsSection,
|
LogsSection,
|
||||||
|
LogsTab,
|
||||||
|
LogsTabs,
|
||||||
LogsTitle,
|
LogsTitle,
|
||||||
LogText,
|
LogText,
|
||||||
LogTimestamp,
|
LogTimestamp,
|
||||||
|
|
@ -16,8 +18,11 @@ export interface UnifiedLogLine {
|
||||||
text: string
|
text: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type LogFilter = 'all' | 'toes'
|
||||||
|
|
||||||
const MAX_LOGS = 200
|
const MAX_LOGS = 200
|
||||||
|
|
||||||
|
let _filter: LogFilter = 'all'
|
||||||
let _logs: UnifiedLogLine[] = []
|
let _logs: UnifiedLogLine[] = []
|
||||||
let _source: EventSource | undefined
|
let _source: EventSource | undefined
|
||||||
|
|
||||||
|
|
@ -54,7 +59,6 @@ function parseLogText(text: string): { method?: string, path?: string, status?:
|
||||||
function LogLineEntry({ log }: { log: UnifiedLogLine }) {
|
function LogLineEntry({ log }: { log: UnifiedLogLine }) {
|
||||||
const parsed = parseLogText(log.text)
|
const parsed = parseLogText(log.text)
|
||||||
const statusColor = getStatusColor(parsed.status)
|
const statusColor = getStatusColor(parsed.status)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LogEntry>
|
<LogEntry>
|
||||||
<LogTimestamp>{formatTime(log.time)}</LogTimestamp>
|
<LogTimestamp>{formatTime(log.time)}</LogTimestamp>
|
||||||
|
|
@ -66,21 +70,31 @@ function LogLineEntry({ log }: { log: UnifiedLogLine }) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const filteredLogs = (): UnifiedLogLine[] =>
|
||||||
|
_filter === 'toes' ? _logs.filter(l => l.app === 'toes') : _logs
|
||||||
|
|
||||||
|
function setFilter(filter: LogFilter) {
|
||||||
|
_filter = filter
|
||||||
|
renderLogs()
|
||||||
|
}
|
||||||
|
|
||||||
function LogsBodyContent() {
|
function LogsBodyContent() {
|
||||||
|
const logs = filteredLogs()
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{_logs.length === 0 ? (
|
{logs.length === 0 ? (
|
||||||
<LogEntry>
|
<LogEntry>
|
||||||
<LogText style={{ color: 'var(--colors-textFaint)' }}>No activity yet</LogText>
|
<LogText style={{ color: 'var(--colors-textFaint)' }}>No activity yet</LogText>
|
||||||
</LogEntry>
|
</LogEntry>
|
||||||
) : (
|
) : (
|
||||||
_logs.map((log, i) => <LogLineEntry key={i} log={log} />)
|
logs.map((log, i) => <LogLineEntry key={i} log={log} />)
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderLogs() {
|
function renderLogs() {
|
||||||
|
update('#unified-logs-tabs', <LogsTabsBar />)
|
||||||
update('#unified-logs-body', <LogsBodyContent />)
|
update('#unified-logs-body', <LogsBodyContent />)
|
||||||
// Auto-scroll after render
|
// Auto-scroll after render
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
|
|
@ -101,11 +115,21 @@ export function initUnifiedLogs() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function LogsTabsBar() {
|
||||||
|
return (
|
||||||
|
<LogsTabs>
|
||||||
|
<LogsTab variant={_filter === 'all' ? 'active' : undefined} onClick={() => setFilter('all')}>All</LogsTab>
|
||||||
|
<LogsTab variant={_filter === 'toes' ? 'active' : undefined} onClick={() => setFilter('toes')}>Toes</LogsTab>
|
||||||
|
</LogsTabs>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export function UnifiedLogs() {
|
export function UnifiedLogs() {
|
||||||
return (
|
return (
|
||||||
<LogsSection>
|
<LogsSection>
|
||||||
<LogsHeader>
|
<LogsHeader>
|
||||||
<LogsTitle>Logs</LogsTitle>
|
<LogsTitle>Logs</LogsTitle>
|
||||||
|
<div id="unified-logs-tabs"><LogsTabsBar /></div>
|
||||||
</LogsHeader>
|
</LogsHeader>
|
||||||
<LogsBody id="unified-logs-body">
|
<LogsBody id="unified-logs-body">
|
||||||
<LogsBodyContent />
|
<LogsBodyContent />
|
||||||
|
|
|
||||||
|
|
@ -97,6 +97,36 @@ export const LogsClearButton = define('LogsClearButton', {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const LogsTabs = define('LogsTabs', {
|
||||||
|
display: 'flex',
|
||||||
|
gap: 0,
|
||||||
|
})
|
||||||
|
|
||||||
|
export const LogsTab = define('LogsTab', {
|
||||||
|
base: 'button',
|
||||||
|
background: 'none',
|
||||||
|
border: 'none',
|
||||||
|
borderBottom: '2px solid transparent',
|
||||||
|
padding: '0 8px 8px',
|
||||||
|
fontSize: 11,
|
||||||
|
fontWeight: 600,
|
||||||
|
color: theme('colors-textFaint'),
|
||||||
|
cursor: 'pointer',
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
letterSpacing: '0.05em',
|
||||||
|
selectors: {
|
||||||
|
'&:hover': {
|
||||||
|
color: theme('colors-text'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
variants: {
|
||||||
|
active: {
|
||||||
|
color: theme('colors-text'),
|
||||||
|
borderBottomColor: theme('colors-text'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
export const LogsBody = define('LogsBody', {
|
export const LogsBody = define('LogsBody', {
|
||||||
height: 200,
|
height: 200,
|
||||||
overflow: 'auto',
|
overflow: 'auto',
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,8 @@ export {
|
||||||
LogsClearButton,
|
LogsClearButton,
|
||||||
LogsHeader,
|
LogsHeader,
|
||||||
LogsSection,
|
LogsSection,
|
||||||
|
LogsTab,
|
||||||
|
LogsTabs,
|
||||||
LogsTitle,
|
LogsTitle,
|
||||||
LogStatus,
|
LogStatus,
|
||||||
LogText,
|
LogText,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { allApps, APPS_DIR, onChange } from '$apps'
|
import { allApps, APPS_DIR, onChange } from '$apps'
|
||||||
|
import { onHostLog } from '../tui'
|
||||||
import { Hype } from '@because/hype'
|
import { Hype } from '@because/hype'
|
||||||
import { cpus, platform, totalmem } from 'os'
|
import { cpus, platform, totalmem } from 'os'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
|
|
@ -258,7 +259,17 @@ function collectLogs() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function pushHostLog(text: string) {
|
||||||
|
const line: UnifiedLogLine = { time: Date.now(), app: 'toes', text }
|
||||||
|
unifiedLogs.push(line)
|
||||||
|
if (unifiedLogs.length > MAX_UNIFIED_LOGS) unifiedLogs.shift()
|
||||||
|
for (const listener of unifiedLogListeners) listener(line)
|
||||||
|
}
|
||||||
|
|
||||||
// Subscribe to app changes to collect logs
|
// Subscribe to app changes to collect logs
|
||||||
onChange(collectLogs)
|
onChange(collectLogs)
|
||||||
|
|
||||||
|
// Subscribe to host-level log messages
|
||||||
|
onHostLog(pushHostLog)
|
||||||
|
|
||||||
export default router
|
export default router
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,13 @@ let _apps: App[] = []
|
||||||
let _enabled = (process.stdout.isTTY ?? false) && !process.env.DEBUG
|
let _enabled = (process.stdout.isTTY ?? false) && !process.env.DEBUG
|
||||||
let _lastRender = 0
|
let _lastRender = 0
|
||||||
let _renderTimer: Timer | undefined
|
let _renderTimer: Timer | undefined
|
||||||
|
let _hostLogListeners = new Set<(text: string) => void>()
|
||||||
let _showEmoji = false
|
let _showEmoji = false
|
||||||
|
|
||||||
|
export const onHostLog = (cb: (text: string) => void) => {
|
||||||
|
_hostLogListeners.add(cb)
|
||||||
|
}
|
||||||
|
|
||||||
export const setShowEmoji = (show: boolean) => {
|
export const setShowEmoji = (show: boolean) => {
|
||||||
_showEmoji = show
|
_showEmoji = show
|
||||||
scheduleRender()
|
scheduleRender()
|
||||||
|
|
@ -21,9 +26,9 @@ export function appLog(app: App, ...msg: string[]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function hostLog(...msg: string[]) {
|
export function hostLog(...msg: string[]) {
|
||||||
if (!_enabled) {
|
const text = msg.join(' ')
|
||||||
console.log('🐾', msg.join(' '))
|
if (!_enabled) console.log('🐾', text)
|
||||||
}
|
for (const listener of _hostLogListeners) listener(text)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setApps(apps: App[]) {
|
export function setApps(apps: App[]) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user