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",
"totalScanned": "Total de pastas encontradas",
"quickScan": "Scan rápido",
"fullScan": "Scan completo"
"fullScan": "Scan completo",
"serverUptime": "Uptime do servidor",
"serverDown": "DESCONECTADO"
},
"player": {
"playListsText": "Fila de Execução",

View File

@ -1,5 +1,7 @@
package events
import "time"
type Event interface {
EventName() string
}
@ -16,3 +18,9 @@ type KeepAlive struct {
}
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)
}
var serverStart time.Time
type broker struct {
// Events are pushed to this channel by the main events-gathering routine
notifier chan []byte
@ -46,6 +48,13 @@ func NewBroker() Broker {
}
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 {
Event `json:"data"`
Name string `json:"name"`
@ -53,9 +62,7 @@ func (broker *broker) SendMessage(event Event) {
pkg.Name = event.EventName()
pkg.Event = event
data, _ := json.Marshal(pkg)
log.Trace("Broker received new event", "name", pkg.Name, "payload", string(data))
broker.notifier <- data
return data
}
func (broker *broker) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
@ -111,27 +118,34 @@ func (broker *broker) listen() {
for {
select {
case s := <-broker.newClients:
// A new client has connected.
// Register their message channel
broker.clients[s] = true
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
// stop sending them messages.
delete(broker.clients, s)
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!
// Send event to all connected clients
for clientMessageChan := range broker.clients {
clientMessageChan <- event
}
case ts := <-keepAlive.C:
// Send a keep alive packet every 15 seconds
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 config from './config'
import { startEventStream } from './eventStream'
import { processEvent } from './actions'
const history = createHashHistory()
if (config.gaTrackingId) {
@ -60,7 +60,7 @@ const App = () => (
const Admin = (props) => {
const dispatch = useDispatch()
if (config.devActivityMenu) {
startEventStream((data) => dispatch(processEvent(data)))
startEventStream(dispatch)
}
return (

View File

@ -1,6 +1,10 @@
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) => {
let type = actionsMap[data.name]
@ -11,8 +15,12 @@ export const processEvent = (data) => {
}
}
export const scanStatusUpdate = (data) =>
processEvent({
name: EVENT_SCAN_STATUS,
data: data,
})
export const scanStatusUpdate = (data) => ({
type: EVENT_SCAN_STATUS,
data: data,
})
export const serverDown = () => ({
type: EVENT_SERVER_START,
data: {},
})

View File

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

View File

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

View File

@ -15,14 +15,18 @@ import {
Box,
} from '@material-ui/core'
import { FiActivity } from 'react-icons/fi'
import { BiError } from 'react-icons/bi'
import { VscSync } from 'react-icons/vsc'
import { GiMagnifyingGlass } from 'react-icons/gi'
import subsonic from '../subsonic'
import { scanStatusUpdate } from '../actions'
import { useInterval } from '../common'
import { formatDuration } from '../utils'
const useStyles = makeStyles((theme) => ({
wrapper: {
position: 'relative',
color: (props) => (props.up ? null : 'orange'),
},
progress: {
color: theme.palette.primary.light,
@ -36,17 +40,31 @@ const useStyles = makeStyles((theme) => ({
zIndex: 2,
},
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 classes = useStyles()
const serverStart = useSelector((state) => state.activity.serverStart)
const up = serverStart && serverStart.startTime
const classes = useStyles({ up })
const translate = useTranslate()
const [anchorEl, setAnchorEl] = useState(null)
const open = Boolean(anchorEl)
const scanStatus = useSelector((state) => state.activity.scanStatus)
const dispatch = useDispatch()
const scanStatus = useSelector((state) => state.activity.scanStatus)
const handleMenuOpen = (event) => setAnchorEl(event.currentTarget)
const handleMenuClose = () => setAnchorEl(null)
@ -70,7 +88,7 @@ const ActivityPanel = () => {
<Tooltip title={translate('activity.title')}>
<IconButton className={classes.button} onClick={handleMenuOpen}>
<Badge badgeContent={null} color="secondary">
<FiActivity size={'20'} />
{up ? <FiActivity size={'20'} /> : <BiError size={'20'} />}
</Badge>
</IconButton>
</Tooltip>
@ -92,6 +110,17 @@ const ActivityPanel = () => {
onClose={handleMenuClose}
>
<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>
<Box display="flex" className={classes.counterStatus}>
<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 = (
previousState = {
scanStatus: { scanning: false, count: 0 },
scanStatus: defaultState,
},
payload
) => {
@ -10,6 +12,13 @@ export const activityReducer = (
switch (type) {
case EVENT_SCAN_STATUS:
return { ...previousState, scanStatus: data }
case EVENT_SERVER_START:
return {
...previousState,
serverStart: {
startTime: data.startTime && Date.parse(data.startTime),
},
}
default:
return previousState
}