chore: use monorepo organization
This commit is contained in:
parent
deaf7d6ecc
commit
a687827f7e
1099 changed files with 8184 additions and 11535 deletions
74
packages/admin-ui/src/pages/Logs/Logs.module.css
Normal file
74
packages/admin-ui/src/pages/Logs/Logs.module.css
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
.titleWrapper {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.tableWrapper {
|
||||
flex: 1;
|
||||
margin-left: 40px;
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
width: 100%;
|
||||
max-width: 78%;
|
||||
max-height: 70vh;
|
||||
}
|
||||
|
||||
.table {
|
||||
white-space: nowrap;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.table th {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.dateColumn {
|
||||
min-width: 160px;
|
||||
}
|
||||
|
||||
.levelColumn {
|
||||
min-width: 100px;
|
||||
}
|
||||
|
||||
.fillColumn {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.shareButton {
|
||||
margin: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 13px;
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
.shareIcon {
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.button {
|
||||
margin: 8px;
|
||||
}
|
||||
|
||||
.titleAndButtonsContainer {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.buttonsWrapper {
|
||||
display: flex;
|
||||
margin-left: 16px;
|
||||
}
|
||||
|
||||
.buttonsWrapper > * {
|
||||
margin: auto 6px;
|
||||
}
|
||||
168
packages/admin-ui/src/pages/Logs/MachineLogs.jsx
Normal file
168
packages/admin-ui/src/pages/Logs/MachineLogs.jsx
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
import { useQuery, gql } from '@apollo/client'
|
||||
import * as R from 'ramda'
|
||||
import React, { useState } from 'react'
|
||||
import LogsDowloaderPopover from 'src/components/LogsDownloaderPopper.jsx'
|
||||
import Title from 'src/components/Title.jsx'
|
||||
import Sidebar from 'src/components/layout/Sidebar.jsx'
|
||||
import { Info3, H4 } from 'src/components/typography/index.jsx'
|
||||
|
||||
import {
|
||||
Table,
|
||||
TableHead,
|
||||
TableRow,
|
||||
TableHeader,
|
||||
TableBody,
|
||||
TableCell
|
||||
} from 'src/components/table/index.js'
|
||||
import { formatDate } from 'src/utils/timezones.js'
|
||||
|
||||
import classes from './Logs.module.css'
|
||||
|
||||
const GET_MACHINES = gql`
|
||||
{
|
||||
machines {
|
||||
name
|
||||
deviceId
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const NUM_LOG_RESULTS = 500
|
||||
|
||||
const GET_MACHINE_LOGS_CSV = gql`
|
||||
query MachineLogs(
|
||||
$deviceId: ID!
|
||||
$limit: Int
|
||||
$from: DateTimeISO
|
||||
$until: DateTimeISO
|
||||
$timezone: String
|
||||
) {
|
||||
machineLogsCsv(
|
||||
deviceId: $deviceId
|
||||
limit: $limit
|
||||
from: $from
|
||||
until: $until
|
||||
timezone: $timezone
|
||||
)
|
||||
}
|
||||
`
|
||||
|
||||
const GET_MACHINE_LOGS = gql`
|
||||
query MachineLogs(
|
||||
$deviceId: ID!
|
||||
$limit: Int
|
||||
$from: DateTimeISO
|
||||
$until: DateTimeISO
|
||||
) {
|
||||
machineLogs(
|
||||
deviceId: $deviceId
|
||||
limit: $limit
|
||||
from: $from
|
||||
until: $until
|
||||
) {
|
||||
logLevel
|
||||
id
|
||||
timestamp
|
||||
message
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const GET_DATA = gql`
|
||||
query getData {
|
||||
config
|
||||
}
|
||||
`
|
||||
|
||||
const Logs = () => {
|
||||
const [selected, setSelected] = useState(null)
|
||||
const [saveMessage, setSaveMessage] = useState(null)
|
||||
|
||||
const deviceId = selected?.deviceId
|
||||
|
||||
const { data: machineResponse, loading: machinesLoading } =
|
||||
useQuery(GET_MACHINES)
|
||||
|
||||
const { data: configResponse, loading: configLoading } = useQuery(GET_DATA)
|
||||
const timezone = R.path(['config', 'locale_timezone'], configResponse)
|
||||
|
||||
const { data: logsResponse, loading: logsLoading } = useQuery(
|
||||
GET_MACHINE_LOGS,
|
||||
{
|
||||
variables: { deviceId, limit: NUM_LOG_RESULTS },
|
||||
skip: !selected,
|
||||
onCompleted: () => setSaveMessage('')
|
||||
}
|
||||
)
|
||||
|
||||
if (machineResponse?.machines?.length && !selected) {
|
||||
setSelected(machineResponse?.machines[0])
|
||||
}
|
||||
|
||||
const isSelected = it => {
|
||||
return R.path(['deviceId'])(selected) === it.deviceId
|
||||
}
|
||||
|
||||
const loading = machinesLoading || configLoading || logsLoading
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={classes.titleWrapper}>
|
||||
<div className={classes.titleAndButtonsContainer}>
|
||||
<Title>Machine logs</Title>
|
||||
{logsResponse && (
|
||||
<div className={classes.buttonsWrapper}>
|
||||
<LogsDowloaderPopover
|
||||
title="Download logs"
|
||||
name={selected.name}
|
||||
query={GET_MACHINE_LOGS_CSV}
|
||||
args={{ deviceId, timezone }}
|
||||
getLogs={logs => R.path(['machineLogsCsv'])(logs)}
|
||||
timezone={timezone}
|
||||
/>
|
||||
<Info3>{saveMessage}</Info3>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className={classes.wrapper}>
|
||||
<Sidebar
|
||||
displayName={it => it.name}
|
||||
data={machineResponse?.machines || []}
|
||||
isSelected={isSelected}
|
||||
onClick={setSelected}
|
||||
/>
|
||||
<div className={classes.tableWrapper}>
|
||||
<Table className={classes.table}>
|
||||
<TableHead>
|
||||
<TableRow header>
|
||||
<TableHeader className={classes.dateColumn}>Date</TableHeader>
|
||||
<TableHeader className={classes.levelColumn}>Level</TableHeader>
|
||||
<TableHeader className={classes.fillColumn} />
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{logsResponse &&
|
||||
logsResponse.machineLogs.map((log, idx) => (
|
||||
<TableRow key={idx} size="sm">
|
||||
<TableCell>
|
||||
{timezone &&
|
||||
formatDate(log.timestamp, timezone, 'yyyy-MM-dd HH:mm')}
|
||||
</TableCell>
|
||||
<TableCell>{log.logLevel}</TableCell>
|
||||
<TableCell>{log.message}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
{loading && <H4>{'Loading...'}</H4>}
|
||||
{!loading && !logsResponse?.machineLogs?.length && (
|
||||
<H4>{'No activity so far'}</H4>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Logs
|
||||
197
packages/admin-ui/src/pages/Logs/ServerLogs.jsx
Normal file
197
packages/admin-ui/src/pages/Logs/ServerLogs.jsx
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
import { useQuery, gql } from '@apollo/client'
|
||||
import * as R from 'ramda'
|
||||
import React, { useState, useRef } from 'react'
|
||||
import LogsDowloaderPopover from 'src/components/LogsDownloaderPopper.jsx'
|
||||
import Title from 'src/components/Title.jsx'
|
||||
import Uptime from 'src/pages/Logs/Uptime.jsx'
|
||||
import { Info3, H4 } from 'src/components/typography/index.jsx'
|
||||
|
||||
import { Select } from 'src/components/inputs/index.js'
|
||||
import {
|
||||
Table,
|
||||
TableHead,
|
||||
TableRow,
|
||||
TableHeader,
|
||||
TableBody,
|
||||
TableCell
|
||||
} from 'src/components/table/index.js'
|
||||
import { startCase } from 'src/utils/string.js'
|
||||
import { formatDate } from 'src/utils/timezones.js'
|
||||
|
||||
import logsClasses from './Logs.module.css'
|
||||
import classes from './ServerLogs.module.css'
|
||||
|
||||
const SHOW_ALL = { code: 'SHOW_ALL', display: 'Show all' }
|
||||
|
||||
const NUM_LOG_RESULTS = 500
|
||||
|
||||
const GET_CSV = gql`
|
||||
query ServerData(
|
||||
$limit: Int
|
||||
$from: DateTimeISO
|
||||
$until: DateTimeISO
|
||||
$timezone: String
|
||||
) {
|
||||
serverLogsCsv(
|
||||
limit: $limit
|
||||
from: $from
|
||||
until: $until
|
||||
timezone: $timezone
|
||||
)
|
||||
}
|
||||
`
|
||||
|
||||
const GET_SERVER_DATA = gql`
|
||||
query ServerData($limit: Int, $from: DateTimeISO, $until: DateTimeISO) {
|
||||
serverVersion
|
||||
uptime {
|
||||
name
|
||||
state
|
||||
uptime
|
||||
}
|
||||
serverLogs(limit: $limit, from: $from, until: $until) {
|
||||
logLevel
|
||||
id
|
||||
timestamp
|
||||
message
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const GET_DATA = gql`
|
||||
query getData {
|
||||
config
|
||||
}
|
||||
`
|
||||
|
||||
const Logs = () => {
|
||||
const tableEl = useRef()
|
||||
|
||||
const [saveMessage, setSaveMessage] = useState(null)
|
||||
const [logLevel, setLogLevel] = useState(SHOW_ALL)
|
||||
|
||||
const { data, loading: dataLoading } = useQuery(GET_SERVER_DATA, {
|
||||
onCompleted: () => setSaveMessage(''),
|
||||
variables: {
|
||||
limit: NUM_LOG_RESULTS
|
||||
}
|
||||
})
|
||||
const { data: configResponse, loading: configLoading } = useQuery(GET_DATA)
|
||||
const timezone = R.path(['config', 'locale_timezone'], configResponse)
|
||||
|
||||
const defaultLogLevels = [
|
||||
{ code: 'error', display: 'Error' },
|
||||
{ code: 'info', display: 'Info' },
|
||||
{ code: 'debug', display: 'Debug' }
|
||||
]
|
||||
const serverVersion = data?.serverVersion
|
||||
const processStates = data?.uptime ?? []
|
||||
|
||||
const getLogLevels = R.compose(
|
||||
R.prepend(SHOW_ALL),
|
||||
R.uniq,
|
||||
R.concat(defaultLogLevels),
|
||||
R.map(it => ({
|
||||
code: R.path(['logLevel'])(it),
|
||||
display: startCase(R.path(['logLevel'])(it))
|
||||
})),
|
||||
R.path(['serverLogs'])
|
||||
)
|
||||
|
||||
const handleLogLevelChange = logLevel => {
|
||||
if (tableEl.current) tableEl.current.scrollTo(0, 0)
|
||||
|
||||
setLogLevel(logLevel)
|
||||
}
|
||||
|
||||
const loading = dataLoading || configLoading
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={logsClasses.titleWrapper}>
|
||||
<div className={logsClasses.titleAndButtonsContainer}>
|
||||
<Title>Server</Title>
|
||||
{data && (
|
||||
<div className={logsClasses.buttonsWrapper}>
|
||||
<LogsDowloaderPopover
|
||||
title="Download logs"
|
||||
name="server-logs"
|
||||
query={GET_CSV}
|
||||
args={{ timezone }}
|
||||
logs={data.serverLogs}
|
||||
getLogs={logs => R.path(['serverLogsCsv'])(logs)}
|
||||
timezone={timezone}
|
||||
/>
|
||||
<Info3>{saveMessage}</Info3>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className={classes.serverVersion}>
|
||||
{serverVersion && <span>Server version: v{serverVersion}</span>}
|
||||
</div>
|
||||
</div>
|
||||
<div className={classes.headerLine2}>
|
||||
{data && (
|
||||
<Select
|
||||
onSelectedItemChange={handleLogLevelChange}
|
||||
label="Level"
|
||||
items={getLogLevels(data)}
|
||||
default={SHOW_ALL}
|
||||
selectedItem={logLevel}
|
||||
/>
|
||||
)}
|
||||
<div className={classes.uptimeContainer}>
|
||||
{processStates &&
|
||||
processStates.map((process, idx) => (
|
||||
<Uptime key={idx} process={process} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className={logsClasses.wrapper}>
|
||||
<div ref={tableEl} className={classes.serverTableWrapper}>
|
||||
<Table className={logsClasses.table}>
|
||||
<TableHead>
|
||||
<TableRow header>
|
||||
<TableHeader className={logsClasses.dateColumn}>
|
||||
Date
|
||||
</TableHeader>
|
||||
<TableHeader className={logsClasses.levelColumn}>
|
||||
Level
|
||||
</TableHeader>
|
||||
<TableHeader className={logsClasses.fillColumn} />
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{data &&
|
||||
data.serverLogs
|
||||
.filter(
|
||||
log =>
|
||||
logLevel === SHOW_ALL || log.logLevel === logLevel.code
|
||||
)
|
||||
.map((log, idx) => (
|
||||
<TableRow key={idx} size="sm">
|
||||
<TableCell>
|
||||
{timezone &&
|
||||
formatDate(
|
||||
log.timestamp,
|
||||
timezone,
|
||||
'yyyy-MM-dd HH:mm'
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell>{log.logLevel}</TableCell>
|
||||
<TableCell>{log.message}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
{loading && <H4>{'Loading...'}</H4>}
|
||||
{!loading && !data?.serverLogs?.length && (
|
||||
<H4>{'No activity so far'}</H4>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Logs
|
||||
20
packages/admin-ui/src/pages/Logs/ServerLogs.module.css
Normal file
20
packages/admin-ui/src/pages/Logs/ServerLogs.module.css
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
.serverTableWrapper {
|
||||
composes: tableWrapper from 'Logs.module.css';
|
||||
max-width: 100%;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.serverVersion {
|
||||
color: var(--comet);
|
||||
margin: auto 0 auto 0;
|
||||
}
|
||||
|
||||
.headerLine2 {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.uptimeContainer {
|
||||
margin: auto 0 auto 0;
|
||||
}
|
||||
33
packages/admin-ui/src/pages/Logs/Uptime.jsx
Normal file
33
packages/admin-ui/src/pages/Logs/Uptime.jsx
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import Chip from '@mui/material/Chip'
|
||||
import * as R from 'ramda'
|
||||
import React from 'react'
|
||||
|
||||
import { onlyFirstToUpper } from 'src/utils/string.js'
|
||||
import { Label1 } from 'src/components/typography/index.jsx'
|
||||
|
||||
const Uptime = ({ process }) => {
|
||||
const uptime = time => {
|
||||
if (time < 60) return `${time}s`
|
||||
if (time < 3600) return `${Math.floor(time / 60)}m`
|
||||
if (time < 86400) return `${Math.floor(time / 60 / 60)}h`
|
||||
return `${Math.floor(time / 60 / 60 / 24)}d`
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="inline-block min-w-26 my-0 mx-5">
|
||||
<Label1 noMargin className="pl-1 color-comet">
|
||||
{R.toLower(process.name)}
|
||||
</Label1>
|
||||
<Chip
|
||||
color={process.state === 'RUNNING' ? 'success' : 'error'}
|
||||
label={
|
||||
process.state === 'RUNNING'
|
||||
? `Running for ${uptime(process.uptime)}`
|
||||
: onlyFirstToUpper(process.state)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Uptime
|
||||
Loading…
Add table
Add a link
Reference in a new issue