Skip to content
Snippets Groups Projects
Commit e44cc99e authored by Matthias Feyll's avatar Matthias Feyll :cookie:
Browse files

(ui): implement UpdateIndicator

parent 7725832e
No related branches found
No related tags found
4 merge requests!1196[renovate] Update module golang.org/x/net to v0.32.0,!1195UI: implement add device functionality,!1167Ui refactor style,!1161Ui refactor style
Pipeline #249668 passed
Showing with 142 additions and 10 deletions
......@@ -5,6 +5,8 @@ import {
} from '@api/api'
import { DeviceViewTabValues } from '@component/devices/view/device.view.tabs'
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
import { refreshUpdateTimer } from '@shared/reducer/routine.reducer'
import { Category, CategoryType } from '@shared/types/category.type'
import { REHYDRATE } from 'redux-persist'
import { RootState } from 'src/stores'
import '../routines/index'
......@@ -129,6 +131,19 @@ startListening({
},
})
startListening({
predicate: (action) => setSelectedMne.match(action),
effect: async (action, listenerApi) => {
listenerApi.dispatch(refreshUpdateTimer(Category.TAB as CategoryType))
},
})
startListening({
predicate: (action) => setDevices.match(action),
effect: async (action, listenerApi) => {
listenerApi.dispatch(refreshUpdateTimer(Category.DEVICE as CategoryType))
},
})
/**
* On startup reset the selected device
......
import { NetworkElementServiceGetAllFlattenedApiArg, api } from '@api/api'
import { setDevices } from '@component/devices/reducer/device.reducer'
import { createAsyncThunk } from '@reduxjs/toolkit'
import { addRoutine } from '@shared/reducer/routine.reducer'
import { setUser } from '@shared/reducer/user.reducer'
import { Category, CategoryType } from '@shared/types/category.type'
import { RootState } from 'src/stores'
import { startListening } from '../../../stores/middleware/listener.middleware'
export const FETCH_DEVICE_ACTION = 'subscription/device/fetchDevices'
// continously fetch devices
const FETCH_DEVICES_INTERVAL = 15000 // in ms
startListening({
actionCreator: setUser,
effect: async (_, listenerApi) => {
listenerApi.dispatch(fetchDevicesThunk())
listenerApi.dispatch(
addRoutine({
thunk: fetchDevicesThunk,
category: Category.DEVICE as CategoryType,
payload: {},
})
)
},
})
const FETCH_DEVICES_INTERVAL = 15000 // in ms
export const fetchDevicesThunk = createAsyncThunk(FETCH_DEVICE_ACTION, (_, thunkApi) => {
const { user } = thunkApi.getState() as RootState
......
import { faGripVertical } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { GridLayout } from '@layout/grid.layout/grid.layout';
import UpdateIndicator from '@layout/grid.layout/update-inidicator.layout/update-indicator.layout';
import { Category, CategoryType } from '@shared/types/category.type';
import { useRef } from 'react';
import { Button, Col, Container, Form, Nav, NavLink, Row } from 'react-bootstrap';
import { useTranslation } from 'react-i18next';
import { useDeviceViewModel } from '../view_model/device.viewmodel';
import './device.scss';
import { DeviceViewTable } from './device.view.table';
import { DeviceViewTabs, DeviceViewTabValues } from './device.view.tabs';
import { DeviceViewTabValues, DeviceViewTabs } from './device.view.tabs';
const DeviceView = () => {
const { t } = useTranslation('common');
......@@ -20,13 +22,16 @@ const DeviceView = () => {
<>
<div key="device-list">
<Container className='c-box hoverable h-100'>
<UpdateIndicator
category={Category.DEVICE as CategoryType}
updateInterval={15000}
/>
<FontAwesomeIcon icon={faGripVertical} className="drag-handle" />
<Row>
<Col sm={12} className='mt-4'>
<h3 className='text-black-50'>{t('device.title')}</h3>
</Col>
</Row>
<Row className='align-items-center'>
<Col xs={12} sm={6}>
<Form.Group controlId='device.search' className='p-0 mx-1 pt-2'>
......@@ -48,6 +53,10 @@ const DeviceView = () => {
<div key="device-details">
<Container className='c-box hoverable h-100'>
<UpdateIndicator
category={Category.TAB as CategoryType}
updateInterval={5000}
/>
<FontAwesomeIcon icon={faGripVertical} className="drag-handle" />
<Row>
<Col xs={12} className='mt-4'>
......@@ -67,7 +76,6 @@ const DeviceView = () => {
</Nav>
</Col>
</Row>
<Row className='align-items-start'>
<Col xs={12}>
{DeviceViewTabs(activeTab)}
......
......@@ -7,7 +7,7 @@ export const useDeviceViewModel = () => {
const { activeTab } = useAppSelector((state) => state.device)
const dispatch = useAppDispatch()
useEffect(() => {}, [])
useEffect(() => { }, [])
const handleActiveTabLink = (tabLink: DeviceViewTabValues) => {
return activeTab === tabLink ? 'active' : ''
......
......@@ -53,6 +53,9 @@
"yang_model": {
"title": "YANG Model"
}
},
"box": {
"lastUpdate": "Last updated {{seconds}} seconds ago"
}
},
"protected": {
......
import { faCircle } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import React, { useState } from 'react'
import { Overlay, Tooltip } from 'react-bootstrap'
import { useTranslation } from 'react-i18next'
import { CategoryType } from '../types'
import { useUpdateIndicatorViewModel } from './update-indicator.viewmodel'
interface UpdateIndicatorProps {
category: CategoryType
updateInterval: number
}
const UpdateIndicator: React.FC<UpdateIndicatorProps> = ({ category, updateInterval }) => {
const [showTooltip, setShowTooltip] = useState(false)
const { t } = useTranslation('common')
const target = React.useRef(null)
const { secondsSinceUpdate, getStatusColor } = useUpdateIndicatorViewModel(category)
return (
<div
className="position-absolute"
style={{
top: 0,
right: '40px',
padding: '10px',
zIndex: 10
}}
>
<div
ref={target}
onMouseEnter={() => setShowTooltip(true)}
onMouseLeave={() => setShowTooltip(false)}
style={{ cursor: 'pointer' }}
>
<FontAwesomeIcon
icon={faCircle}
className={getStatusColor(updateInterval)}
size="sm"
/>
</div>
<Overlay target={target.current} show={showTooltip} placement="bottom">
<Tooltip id="update-tooltip">
{t('device.box.lastUpdate', { seconds: secondsSinceUpdate })}
</Tooltip>
</Overlay>
</div>
)
}
export default UpdateIndicator
\ No newline at end of file
import { useAppSelector } from "@hooks"
import { CategoryType } from "@shared/types/category.type"
import { useEffect, useState } from 'react'
export const useUpdateIndicatorViewModel = (category: CategoryType) => {
const { thunks } = useAppSelector((state) => state.routine)
const [secondsSinceUpdate, setSecondsSinceUpdate] = useState<number>(-1)
useEffect(() => {
const updateTimer = () => {
const lastupdate = thunks[category]?.lastupdate
if (lastupdate) {
setSecondsSinceUpdate(Math.round((Date.now() - lastupdate) / 1000))
} else {
setSecondsSinceUpdate(-1)
}
}
// Initial update
updateTimer()
// Set up interval for updates
const intervalId = setInterval(updateTimer, 1000)
return () => clearInterval(intervalId)
}, [category, thunks])
const getStatusColor = (updateInterval: number) => {
const updateIntervalSeconds = updateInterval / 1000
if (secondsSinceUpdate > updateIntervalSeconds * 0.9) return "text-primary"
if (secondsSinceUpdate > updateIntervalSeconds * 1.3) return "text-danger"
return "text-bg-primary"
}
return {
secondsSinceUpdate,
getStatusColor
}
}
\ No newline at end of file
......@@ -16,6 +16,7 @@ const initialState: ReducerState = {
TABLE: null,
TAB: null
},
}
......@@ -27,19 +28,24 @@ const RoutineSlice = createSlice({
const thunk: ThunkPersist = {
category: payload.category,
payload: payload.payload,
thunkId: payload.thunk.id
thunkId: payload.thunk.id,
lastupdate: Date.now()
}
state.thunks[payload.category] = thunk
},
refreshUpdateTimer: (state: any, { payload }: PayloadAction<CategoryType>) => {
state.thunks[payload].lastupdate = Date.now()
},
removeAll: (state) => {
state.thunks = initialState.thunks
},
},
})
export const { addRoutine } = RoutineSlice.actions
export const { addRoutine, refreshUpdateTimer } = RoutineSlice.actions
// on logout remove all routine
startListening({
......
......@@ -2,7 +2,7 @@ $theme-colors: (
"primary": #b350e0,
"primary::hover": #ddaff3af,
"bg-primary": #ededed,
"danger": #ffdcdc,
"danger": #ff0000,
"warning": #dbd116,
"dark": #595959,
"black": #000000
......
......@@ -19,5 +19,6 @@ export interface ThunkDTO {
export interface ThunkPersist {
thunkId: number,
payload: Object
category: CategoryType
category: CategoryType,
lastupdate: number
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment