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