mirror of
https://github.com/navidrome/navidrome.git
synced 2025-04-13 10:47:19 +03:00
Add Uptime to Activity Panel
This commit is contained in:
parent
b64bb706f7
commit
08f96639f4
@ -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",
|
||||
|
@ -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" }
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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 (
|
||||
|
@ -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: {},
|
||||
})
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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",
|
||||
|
@ -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}>
|
||||
|
@ -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
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user