Add Uptime to Activity Panel

This commit is contained in:
Deluan 2020-11-13 18:40:33 -05:00
parent b64bb706f7
commit 08f96639f4
9 changed files with 118 additions and 35 deletions

View File

@ -291,7 +291,9 @@
"title": "Atividade", "title": "Atividade",
"totalScanned": "Total de pastas encontradas", "totalScanned": "Total de pastas encontradas",
"quickScan": "Scan rápido", "quickScan": "Scan rápido",
"fullScan": "Scan completo" "fullScan": "Scan completo",
"serverUptime": "Uptime do servidor",
"serverDown": "DESCONECTADO"
}, },
"player": { "player": {
"playListsText": "Fila de Execução", "playListsText": "Fila de Execução",

View File

@ -1,5 +1,7 @@
package events package events
import "time"
type Event interface { type Event interface {
EventName() string EventName() string
} }
@ -16,3 +18,9 @@ type KeepAlive struct {
} }
func (s KeepAlive) EventName() string { return "keepAlive" } func (s KeepAlive) EventName() string { return "keepAlive" }
type ServerStart struct {
StartTime time.Time `json:"startTime"`
}
func (s ServerStart) EventName() string { return "serverStart" }

View File

@ -16,6 +16,8 @@ type Broker interface {
SendMessage(event Event) SendMessage(event Event)
} }
var serverStart time.Time
type broker struct { type broker struct {
// Events are pushed to this channel by the main events-gathering routine // Events are pushed to this channel by the main events-gathering routine
notifier chan []byte notifier chan []byte
@ -46,6 +48,13 @@ func NewBroker() Broker {
} }
func (broker *broker) SendMessage(event Event) { func (broker *broker) SendMessage(event Event) {
data := broker.formatEvent(event)
log.Trace("Broker received new event", "name", event.EventName(), "payload", string(data))
broker.notifier <- data
}
func (broker *broker) formatEvent(event Event) []byte {
pkg := struct { pkg := struct {
Event `json:"data"` Event `json:"data"`
Name string `json:"name"` Name string `json:"name"`
@ -53,9 +62,7 @@ func (broker *broker) SendMessage(event Event) {
pkg.Name = event.EventName() pkg.Name = event.EventName()
pkg.Event = event pkg.Event = event
data, _ := json.Marshal(pkg) data, _ := json.Marshal(pkg)
return data
log.Trace("Broker received new event", "name", pkg.Name, "payload", string(data))
broker.notifier <- data
} }
func (broker *broker) ServeHTTP(rw http.ResponseWriter, req *http.Request) { func (broker *broker) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
@ -111,27 +118,34 @@ func (broker *broker) listen() {
for { for {
select { select {
case s := <-broker.newClients: case s := <-broker.newClients:
// A new client has connected. // A new client has connected.
// Register their message channel // Register their message channel
broker.clients[s] = true broker.clients[s] = true
log.Debug("Client added to event broker", "numClients", len(broker.clients)) log.Debug("Client added to event broker", "numClients", len(broker.clients))
case s := <-broker.closingClients:
// Send a serverStart event to new client
s <- broker.formatEvent(&ServerStart{serverStart})
case s := <-broker.closingClients:
// A client has dettached and we want to // A client has dettached and we want to
// stop sending them messages. // stop sending them messages.
delete(broker.clients, s) delete(broker.clients, s)
log.Debug("Removed client from event broker", "numClients", len(broker.clients)) log.Debug("Removed client from event broker", "numClients", len(broker.clients))
case event := <-broker.notifier:
case event := <-broker.notifier:
// We got a new event from the outside! // We got a new event from the outside!
// Send event to all connected clients // Send event to all connected clients
for clientMessageChan := range broker.clients { for clientMessageChan := range broker.clients {
clientMessageChan <- event clientMessageChan <- event
} }
case ts := <-keepAlive.C: case ts := <-keepAlive.C:
// Send a keep alive packet every 15 seconds // Send a keep alive packet every 15 seconds
broker.SendMessage(&KeepAlive{TS: ts.Unix()}) broker.SendMessage(&KeepAlive{TS: ts.Unix()})
} }
} }
} }
func init() {
serverStart = time.Now()
}

View File

@ -27,7 +27,7 @@ import createAdminStore from './store/createAdminStore'
import { i18nProvider } from './i18n' import { i18nProvider } from './i18n'
import config from './config' import config from './config'
import { startEventStream } from './eventStream' import { startEventStream } from './eventStream'
import { processEvent } from './actions'
const history = createHashHistory() const history = createHashHistory()
if (config.gaTrackingId) { if (config.gaTrackingId) {
@ -60,7 +60,7 @@ const App = () => (
const Admin = (props) => { const Admin = (props) => {
const dispatch = useDispatch() const dispatch = useDispatch()
if (config.devActivityMenu) { if (config.devActivityMenu) {
startEventStream((data) => dispatch(processEvent(data))) startEventStream(dispatch)
} }
return ( return (

View File

@ -1,6 +1,10 @@
export const EVENT_SCAN_STATUS = 'EVENT_SCAN_STATUS' export const EVENT_SCAN_STATUS = 'EVENT_SCAN_STATUS'
export const EVENT_SERVER_START = 'EVENT_SERVER_START'
const actionsMap = { scanStatus: EVENT_SCAN_STATUS } const actionsMap = {
scanStatus: EVENT_SCAN_STATUS,
serverStart: EVENT_SERVER_START,
}
export const processEvent = (data) => { export const processEvent = (data) => {
let type = actionsMap[data.name] let type = actionsMap[data.name]
@ -11,8 +15,12 @@ export const processEvent = (data) => {
} }
} }
export const scanStatusUpdate = (data) => export const scanStatusUpdate = (data) => ({
processEvent({ type: EVENT_SCAN_STATUS,
name: EVENT_SCAN_STATUS, data: data,
data: data, })
})
export const serverDown = () => ({
type: EVENT_SERVER_START,
data: {},
})

View File

@ -1,9 +1,13 @@
import { baseUrl } from './utils' import { baseUrl } from './utils'
import throttle from 'lodash.throttle' import throttle from 'lodash.throttle'
import { processEvent, serverDown } from './actions'
let es = null let es = null
let onMessageHandler = null let dispatch = null
let timeOut = null let timeout = null
const defaultIntervalCheck = 20000
const errorIntervalCheck = 2000
let currentIntervalCheck = defaultIntervalCheck
const getEventStream = () => { const getEventStream = () => {
if (es === null) { if (es === null) {
@ -15,22 +19,23 @@ const getEventStream = () => {
} }
// Reestablish the event stream after 20 secs of inactivity // Reestablish the event stream after 20 secs of inactivity
const setTimeout = () => { const setTimeout = (value) => {
if (timeOut != null) { currentIntervalCheck = value
window.clearTimeout(timeOut) if (timeout != null) {
window.clearTimeout(timeout)
} }
timeOut = window.setTimeout(() => { timeout = window.setTimeout(() => {
if (es != null) { if (es != null) {
es.close() es.close()
} }
es = null es = null
startEventStream(onMessageHandler) startEventStream(dispatch)
}, 20000) }, currentIntervalCheck)
} }
export const startEventStream = (messageHandler) => { export const startEventStream = (dispatchFunc) => {
onMessageHandler = messageHandler dispatch = dispatchFunc
setTimeout() setTimeout(currentIntervalCheck)
if (!localStorage.getItem('token')) { if (!localStorage.getItem('token')) {
console.log('Cannot create a unauthenticated EventSource') console.log('Cannot create a unauthenticated EventSource')
return return
@ -40,11 +45,17 @@ export const startEventStream = (messageHandler) => {
(msg) => { (msg) => {
const data = JSON.parse(msg.data) const data = JSON.parse(msg.data)
if (data.name !== 'keepAlive') { if (data.name !== 'keepAlive') {
onMessageHandler(data) dispatch(processEvent(data))
} }
setTimeout() // Reset timeout on every received message setTimeout(defaultIntervalCheck) // Reset timeout on every received message
}, },
100, 100,
{ trailing: true } { trailing: true }
) )
es.onerror = (e) => {
setTimeout(errorIntervalCheck)
dispatch(serverDown())
}
return es
} }

View File

@ -291,7 +291,9 @@
"title": "Activity", "title": "Activity",
"totalScanned": "Total Folders Scanned", "totalScanned": "Total Folders Scanned",
"quickScan": "Quick Scan", "quickScan": "Quick Scan",
"fullScan": "Full Scan" "fullScan": "Full Scan",
"serverUptime": "Server Uptime",
"serverDown": "OFFLINE"
}, },
"player": { "player": {
"playListsText": "Play Queue", "playListsText": "Play Queue",

View File

@ -15,14 +15,18 @@ import {
Box, Box,
} from '@material-ui/core' } from '@material-ui/core'
import { FiActivity } from 'react-icons/fi' import { FiActivity } from 'react-icons/fi'
import { BiError } from 'react-icons/bi'
import { VscSync } from 'react-icons/vsc' import { VscSync } from 'react-icons/vsc'
import { GiMagnifyingGlass } from 'react-icons/gi' import { GiMagnifyingGlass } from 'react-icons/gi'
import subsonic from '../subsonic' import subsonic from '../subsonic'
import { scanStatusUpdate } from '../actions' import { scanStatusUpdate } from '../actions'
import { useInterval } from '../common'
import { formatDuration } from '../utils'
const useStyles = makeStyles((theme) => ({ const useStyles = makeStyles((theme) => ({
wrapper: { wrapper: {
position: 'relative', position: 'relative',
color: (props) => (props.up ? null : 'orange'),
}, },
progress: { progress: {
color: theme.palette.primary.light, color: theme.palette.primary.light,
@ -36,17 +40,31 @@ const useStyles = makeStyles((theme) => ({
zIndex: 2, zIndex: 2,
}, },
counterStatus: { counterStatus: {
minWidth: '16em', minWidth: '15em',
}, },
})) }))
const getUptime = (serverStart) =>
formatDuration((Date.now() - serverStart.startTime) / 1000)
const Uptime = () => {
const serverStart = useSelector((state) => state.activity.serverStart)
const [uptime, setUptime] = useState(getUptime(serverStart))
useInterval(() => {
setUptime(getUptime(serverStart))
}, 1000)
return <span>{uptime}</span>
}
const ActivityPanel = () => { const ActivityPanel = () => {
const classes = useStyles() const serverStart = useSelector((state) => state.activity.serverStart)
const up = serverStart && serverStart.startTime
const classes = useStyles({ up })
const translate = useTranslate() const translate = useTranslate()
const [anchorEl, setAnchorEl] = useState(null) const [anchorEl, setAnchorEl] = useState(null)
const open = Boolean(anchorEl) const open = Boolean(anchorEl)
const scanStatus = useSelector((state) => state.activity.scanStatus)
const dispatch = useDispatch() const dispatch = useDispatch()
const scanStatus = useSelector((state) => state.activity.scanStatus)
const handleMenuOpen = (event) => setAnchorEl(event.currentTarget) const handleMenuOpen = (event) => setAnchorEl(event.currentTarget)
const handleMenuClose = () => setAnchorEl(null) const handleMenuClose = () => setAnchorEl(null)
@ -70,7 +88,7 @@ const ActivityPanel = () => {
<Tooltip title={translate('activity.title')}> <Tooltip title={translate('activity.title')}>
<IconButton className={classes.button} onClick={handleMenuOpen}> <IconButton className={classes.button} onClick={handleMenuOpen}>
<Badge badgeContent={null} color="secondary"> <Badge badgeContent={null} color="secondary">
<FiActivity size={'20'} /> {up ? <FiActivity size={'20'} /> : <BiError size={'20'} />}
</Badge> </Badge>
</IconButton> </IconButton>
</Tooltip> </Tooltip>
@ -92,6 +110,17 @@ const ActivityPanel = () => {
onClose={handleMenuClose} onClose={handleMenuClose}
> >
<Card> <Card>
<CardContent>
<Box display="flex" className={classes.counterStatus}>
<Box component="span" flex={2}>
{translate('activity.serverUptime')}:
</Box>
<Box component="span" flex={1}>
{up ? <Uptime /> : translate('activity.serverDown')}
</Box>
</Box>
</CardContent>
<Divider />
<CardContent> <CardContent>
<Box display="flex" className={classes.counterStatus}> <Box display="flex" className={classes.counterStatus}>
<Box component="span" flex={2}> <Box component="span" flex={2}>

View File

@ -1,8 +1,10 @@
import { EVENT_SCAN_STATUS } from '../actions' import { EVENT_SCAN_STATUS, EVENT_SERVER_START } from '../actions'
const defaultState = { scanStatus: { scanning: false, count: 0 } }
export const activityReducer = ( export const activityReducer = (
previousState = { previousState = {
scanStatus: { scanning: false, count: 0 }, scanStatus: defaultState,
}, },
payload payload
) => { ) => {
@ -10,6 +12,13 @@ export const activityReducer = (
switch (type) { switch (type) {
case EVENT_SCAN_STATUS: case EVENT_SCAN_STATUS:
return { ...previousState, scanStatus: data } return { ...previousState, scanStatus: data }
case EVENT_SERVER_START:
return {
...previousState,
serverStart: {
startTime: data.startTime && Date.parse(data.startTime),
},
}
default: default:
return previousState return previousState
} }